├── .gitignore ├── LICENSE ├── README.md ├── bleu-station ├── README.md ├── bleu-station.js ├── bleu-station.txt ├── pseudo.js └── test.js ├── estimote-sticker ├── estimote-sticker.js ├── pseudo.js └── test.js ├── estimote ├── README.md ├── estimote.js ├── estimote.txt ├── pseudo.js └── test.js ├── index.js ├── lib └── bleacon.js ├── package.json ├── radbeacon ├── pseudo-radbeacon-tag.js ├── pseudo-radbeacon-usb.js ├── radbeacon-tag.js ├── radbeacon-tag.txt ├── radbeacon-usb.txt └── test-radbeacon-tag.js └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | 16 | node_modules/ 17 | 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Sandeep Mistry 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-bleacon 2 | 3 | [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/sandeepmistry/node-bleacon?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | 5 | 6 | A Node.js library for creating, discovering, and configuring iBeacons 7 | 8 | ## Prerequisites 9 | 10 | * See [noble prerequisites](https://github.com/sandeepmistry/noble#prerequisites) and [bleno prerequisites](https://github.com/sandeepmistry/bleno#prerequisites) for your platform 11 | 12 | ## Install 13 | 14 | ```sh 15 | npm install bleacon 16 | ``` 17 | 18 | ## Usage 19 | 20 | ```javascript 21 | var Bleacon = require('bleacon'); 22 | ``` 23 | 24 | ### Start advertising 25 | 26 | "Create" an iBeacon 27 | 28 | ```javascript 29 | var uuid = 'e2c56db5dffb48d2b060d0f5a71096e0'; 30 | var major = 0; // 0 - 65535 31 | var minor = 0; // 0 - 65535 32 | var measuredPower = -59; // -128 - 127 (measured RSSI at 1 meter) 33 | 34 | Bleacon.startAdvertising(uuid, major, minor, measuredPower); 35 | ``` 36 | 37 | ### Stop advertising 38 | 39 | Stop your iBeacon 40 | 41 | ```javascript 42 | Bleacon.stopAdvertising(); 43 | ``` 44 | 45 | ### Start scanning 46 | 47 | ```javascript 48 | var uuid = 'e2c56db5dffb48d2b060d0f5a71096e0'; 49 | var major = 0; // 0 - 65535 50 | var minor = 0; // 0 - 65535 51 | 52 | Bleacon.startScanning([uuid], [major], [minor]); 53 | ``` 54 | 55 | Examples 56 | 57 | ```javascript 58 | Bleacon.startScanning(); // scan for any bleacons 59 | 60 | Bleacon.startScanning(uuid); // scan for bleacons with a particular uuid 61 | 62 | Bleacon.startScanning(uuid, major); // scan for bleacons with a particular uuid and major 63 | 64 | Bleacon.startScanning(uuid, major, minor); // scan for bleacons with a particular uuid. major, and minor 65 | ``` 66 | 67 | ### Stop scanning 68 | 69 | ```javascript 70 | Bleacon.stopScanning(); 71 | ``` 72 | 73 | ### Events 74 | 75 | ```javascript 76 | Bleacon.on('discover', function(bleacon) { 77 | // ... 78 | }); 79 | ``` 80 | 81 | ```bleacon``` properties: 82 | 83 | * uuid 84 | * advertised uuid 85 | * major 86 | * advertised major 87 | * minor 88 | * advertised minor 89 | * measuredPower 90 | * advertised measured RSSI at 1 meter away 91 | * rssi 92 | * current RSSI 93 | * accuracy 94 | * +/- meters, based on measuredPower and RSSI 95 | * proximity 96 | * current proximity ('unknown', 'immediate', 'near', or 'far') 97 | 98 | ## Configuring 99 | 100 | * [Bleu Station](https://github.com/sandeepmistry/node-bleacon/tree/master/bleu-station) 101 | * [Estimote](https://github.com/sandeepmistry/node-bleacon/tree/master/estimote) 102 | 103 | ## iBeacon Advertisement format 104 | 105 | __Note:__ not official, determined using [noble](https://github.com/sandeepmistry/noble), and the [AirLocate](http://adcdownload.apple.com/wwdc_2013/wwdc_2013_sample_code/ios_airlocate.zip) example. 106 | 107 | Following data is in the manufacturer data section of the advertisment data 108 | 109 | ``` 110 | 111 | ``` 112 | 113 | Example: 114 | 115 | ``` 116 | 4C00 02 15 585CDE931B0142CC9A1325009BEDC65E 0000 0000 C5 117 | ``` 118 | 119 | * Apple [Company Identifier](https://www.bluetooth.org/en-us/specification/assigned-numbers/company-identifiers) (Little Endian) 120 | * data type, 0x02 => iBeacon 121 | * data length, 0x15 = 21 122 | * uuid: ```585CDE931B0142CC9A1325009BEDC65E``` 123 | * major: ```0000``` 124 | * minor: ```0000``` 125 | * meaured power at 1 meter: ```0xc5``` = ```-59``` 126 | 127 | [![Analytics](https://ga-beacon.appspot.com/UA-56089547-1/sandeepmistry/node-bleacon?pixel)](https://github.com/igrigorik/ga-beacon) 128 | -------------------------------------------------------------------------------- /bleu-station/README.md: -------------------------------------------------------------------------------- 1 | bleacon - Bleu Station 2 | ====================== 3 | 4 | Configure [Twocanoes](https://twocanoes.com) [Bleu Station](https://twocanoes.com/bleu) iBeacons. 5 | 6 | 7 | Usage 8 | ----- 9 | 10 | var BleuStation = require('bleacon').BleuStation; 11 | 12 | __Discover__ 13 | 14 | BleuStation.discover(callback(bleuStation)); 15 | 16 | __Connect__ 17 | 18 | bleuStation.connect(callback); 19 | 20 | __Disconnect__ 21 | 22 | bleuStation.disconnect(callback); 23 | 24 | __Discover Services and Characteristics__ 25 | 26 | Run after connect. 27 | 28 | bleuStation.discoverServicesAndCharacteristics(callback); 29 | 30 | __Login__ 31 | 32 | Run after discover services and characteristics prior to write operations. 33 | 34 | var password = 'qwert123'; // default password 35 | bleuStation.login(password, callback(success)); // success is a boolean 36 | 37 | __Device Info__ 38 | 39 | bleuStation.readDeviceName(callback(deviceName)); 40 | 41 | bleuStation.readManufacturerName(callback(manufacturerName)); 42 | 43 | bleuStation.readModelNumber(callback(modelNumber)); 44 | 45 | bleuStation.readHardwareRevision(callback(hardwareRevision)); 46 | 47 | bleuStation.readFirmwareRevision(callback(firmwareRevision)); 48 | 49 | __iBeacon__ 50 | 51 | // UUID 52 | bleuStation.readUuid(callback(uuid)); 53 | 54 | var uuid = 'e2c56db5dffb48d2b060d0f5a71096e0'; 55 | bleuStation.writeUuid(uuid, callback); 56 | 57 | // Major 58 | bleuStation.readMajor(callback(major)); 59 | 60 | var major = 0x0001; // 0 - 65535 61 | bleuStation.writeMajor(major, callback); 62 | 63 | // Minor 64 | bleuStation.readMinor(callback(minor)); 65 | 66 | var minor = 0x0002; // 0 - 65535 67 | bleuStation.writeMinor(minor, callback); 68 | 69 | // Measured power 70 | bleuStation.readMeasuredTxPower(callback(measuredTxPower)); 71 | 72 | var measuredTxPower = -60; // -128 - 127 73 | bleuStation.writeMeasuredTxPower(measuredTxPower, callback); 74 | 75 | __Admin__ 76 | 77 | // TX Power 78 | bleuStation.readTxPower(callback(txPower)); 79 | 80 | var txPower = 100; // 25 - 100 81 | bleuStation.writeTxPower(txPower, callback); 82 | 83 | // Admin. device name 84 | bleuStation.readAdminDeviceName(callback(adminDeviceName)); 85 | 86 | var adminDeviceName = 'ADM'; 87 | bleuStation.writeAdminDeviceName(adminDeviceName, callback); 88 | 89 | // Admin. password (used for login) 90 | var adminPassword = 'qwerty123'; 91 | bleuStation.writeAdminPassword(adminPassword, callback); 92 | 93 | // Version (config) 94 | bleuStation.readVersionNumber(callback(versionNumber)); 95 | 96 | __Other__ 97 | 98 | // latitude 99 | bleuStation.readLatitude(callback(latitude)); 100 | 101 | var latitude = 123.456789; 102 | bleuStation.writeLatitude(latitude, callback); 103 | 104 | // longitude 105 | bleuStation.readLongitude(callback(tlongitude)); 106 | 107 | var longitude = 999.123456; 108 | bleuStation.writeLongitude(longitude, callback); 109 | 110 | // URL 1 111 | bleuStation.readURL1(callback(url1)); 112 | 113 | var url1 = 'https://twocanoes.com/bleu/config/default.plist'; 114 | bleuStation.writeURL1(url1, callback); 115 | 116 | // URL 2 117 | bleuStation.readURL2(callback(url1)); 118 | 119 | var url2 = ''; 120 | bleuStation.writeURL2(url2, callback); 121 | 122 | Events 123 | ------ 124 | 125 | __Disconnect__ 126 | 127 | bleuStation.on('disconnect', callback); 128 | -------------------------------------------------------------------------------- /bleu-station/bleu-station.js: -------------------------------------------------------------------------------- 1 | var events = require('events'); 2 | var util = require('util'); 3 | 4 | var debug = require('debug')('bleu-station'); 5 | 6 | var noble = require('noble'); 7 | 8 | var DEVICE_NAME_UUID = '2a00'; 9 | 10 | var MANUFACTURER_NAME_UUID = '2a29'; 11 | var MODEL_NUMBER_UUID = '2a24'; 12 | var HARDWARE_REVISION_UUID = '2a27'; 13 | var FIRMWARE_REVISION_UUID = '2a26'; 14 | 15 | var IBEACON_UUID_UUID = 'b0702881a295a8abf734031a98a512de'; 16 | var IBEACON_MAJOR_UUID = 'b0702882a295a8abf734031a98a512de'; 17 | var IBEACON_MINOR_UUID = 'b0702883a295a8abf734031a98a512de'; 18 | var IBEACON_MEASURED_TX_POWER_UUID = 'b0702884a295a8abf734031a98a512de'; 19 | 20 | var ADMIN_IBEACON_UUID_UUID = 'c8f21a07078a42df86600946ffd109be'; 21 | var ADMIN_IBEACON_MAJOR_UUID = '677ec16a743d42fcafe1d9f4a02a726f'; 22 | var ADMIN_IBEACON_MINOR_UUID = '7722712a07f4433f8e305a6dc26356ba'; 23 | var ADMIN_TX_POWER_UUID = '8aa2414e8e614d9cae14508ee3192dee'; 24 | var ADMIN_TX_POWER_CAL_UUID = '0b4700c35c5346519601b7e1e06b1bbf'; 25 | var ADMIN_DEVICE_NAME_UUID = '980ac81e94fd43f48b9a260a65dd3adc'; 26 | var ADMIN_PASSWORD_UUID = '12d8cca8b1cc4e48abf7767b5e0f3ff6'; 27 | 28 | var LATITUDE_UUID = 'fffc3dbb92d148f1aa5289a3d9517d79'; 29 | var LONGITUDE_UUID = '9e5f3adf337f4acfb54d929da486f512'; 30 | var VERSION_NUMBER_UUID = '676e5ff15cd7484ab98f97f3c96e6361'; 31 | 32 | var URL_1_UUID = '98a5a965efd34d16924718fd88bb8a30'; 33 | var URL_2_UUID = '3e8501d8aa3943368908b6e79d81a050'; 34 | 35 | var BleuStation = function(peripheral) { 36 | this._peripheral = peripheral; 37 | this._services = {}; 38 | this._characteristics = {}; 39 | 40 | this.uuid = peripheral.uuid; 41 | this.name = peripheral.advertisement.localName; 42 | 43 | this._peripheral.on('disconnect', this.onDisconnect.bind(this)); 44 | }; 45 | 46 | util.inherits(BleuStation, events.EventEmitter); 47 | 48 | BleuStation.is = function(peripheral) { 49 | var localName = peripheral.advertisement.localName; 50 | 51 | return (localName && localName.length === 8 && localName.indexOf('TC') === 0) || 52 | (localName === undefined && peripheral.advertisement.manufacturerData); 53 | }; 54 | 55 | BleuStation.discover = function(callback) { 56 | var startScanningOnPowerOn = function() { 57 | if (noble.state === 'poweredOn') { 58 | var onDiscover = function(peripheral) { 59 | if (!BleuStation.is(peripheral)) { 60 | return; 61 | } 62 | 63 | noble.removeListener('discover', onDiscover); 64 | 65 | noble.stopScanning(); 66 | 67 | var bleuStation = new BleuStation(peripheral); 68 | 69 | callback(bleuStation); 70 | }; 71 | 72 | noble.on('discover', onDiscover); 73 | 74 | noble.startScanning([], true); 75 | } else { 76 | noble.once('stateChange', startScanningOnPowerOn); 77 | } 78 | }; 79 | 80 | startScanningOnPowerOn(); 81 | }; 82 | 83 | BleuStation.prototype.toString = function() { 84 | return JSON.stringify({ 85 | uuid: this.uuid, 86 | name: this.name 87 | }); 88 | }; 89 | 90 | BleuStation.prototype.onDisconnect = function() { 91 | this.emit('disconnect'); 92 | }; 93 | 94 | BleuStation.prototype.connect = function(callback) { 95 | this._peripheral.connect(callback); 96 | }; 97 | 98 | BleuStation.prototype.disconnect = function(callback) { 99 | this._peripheral.disconnect(callback); 100 | }; 101 | 102 | BleuStation.prototype.discoverServicesAndCharacteristics = function(callback) { 103 | this._peripheral.discoverAllServicesAndCharacteristics(function(error, services, characteristics) { 104 | if (error === null) { 105 | for (var i in services) { 106 | var service = services[i]; 107 | this._services[service.uuid] = service; 108 | } 109 | 110 | for (var j in characteristics) { 111 | var characteristic = characteristics[j]; 112 | 113 | this._characteristics[characteristic.uuid] = characteristic; 114 | } 115 | } 116 | 117 | callback(error); 118 | }.bind(this)); 119 | }; 120 | 121 | BleuStation.prototype.readDataCharacteristic = function(uuid, callback) { 122 | this._characteristics[uuid].read(function(error, data) { 123 | callback(data); 124 | }); 125 | }; 126 | 127 | BleuStation.prototype.readStringCharacteristic = function(uuid, callback) { 128 | this.readDataCharacteristic(uuid, function(data) { 129 | if (data[0] === data.length - 1) { 130 | data = data.slice(1); 131 | } 132 | 133 | callback(data.toString()); 134 | }); 135 | }; 136 | 137 | BleuStation.prototype.readUInt16Characteristic = function(uuid, callback) { 138 | this.readDataCharacteristic(uuid, function(data) { 139 | callback(data.readUInt16BE(0)); 140 | }); 141 | }; 142 | 143 | BleuStation.prototype.readInt8Characteristic = function(uuid, callback) { 144 | this.readDataCharacteristic(uuid, function(data) { 145 | callback(data.readInt8(0)); 146 | }); 147 | }; 148 | 149 | BleuStation.prototype.readUInt8Characteristic = function(uuid, callback) { 150 | this.readDataCharacteristic(uuid, function(data) { 151 | callback(data.readUInt8(0)); 152 | }); 153 | }; 154 | 155 | BleuStation.prototype.readDoubleCharacteristic = function(uuid, callback) { 156 | this.readDataCharacteristic(uuid, function(data) { 157 | callback(data.readDoubleLE(0)); 158 | }); 159 | }; 160 | 161 | BleuStation.prototype.writeDataCharacteristic = function(uuid, data, callback) { 162 | this._characteristics[uuid].write(data, false, callback); 163 | }; 164 | 165 | BleuStation.prototype.writeStringCharacteristic = function(uuid, value, callback) { 166 | var valueLength = value.length; 167 | var data = new Buffer(valueLength + 1); 168 | var valueData = new Buffer(value); 169 | 170 | data[0] = valueLength; 171 | for (var i = 0; i < valueLength; i++) { 172 | data[i + 1] = valueData[i]; 173 | } 174 | 175 | this.writeDataCharacteristic(uuid, data, callback); 176 | }; 177 | 178 | BleuStation.prototype.writeUInt16Characteristic = function(uuid, value, callback) { 179 | var data = new Buffer(2); 180 | 181 | data.writeUInt16BE(value, 0); 182 | 183 | this.writeDataCharacteristic(uuid, data, callback); 184 | }; 185 | 186 | BleuStation.prototype.writeInt8Characteristic = function(uuid, value, callback) { 187 | var data = new Buffer(1); 188 | 189 | data.writeInt8(value, 0); 190 | 191 | this.writeDataCharacteristic(uuid, data, callback); 192 | }; 193 | 194 | BleuStation.prototype.writeUInt8Characteristic = function(uuid, value, callback) { 195 | var data = new Buffer(1); 196 | 197 | data.writeUInt8(value, 0); 198 | 199 | this.writeDataCharacteristic(uuid, data, callback); 200 | }; 201 | 202 | BleuStation.prototype.writeDoubleCharacteristic = function(uuid, value, callback) { 203 | var data = new Buffer(8); 204 | 205 | data.writeDoubleLE(value, 0); 206 | 207 | this.writeDataCharacteristic(uuid, data, callback); 208 | }; 209 | 210 | BleuStation.prototype.login = function(password, callback) { 211 | this._characteristics[ADMIN_PASSWORD_UUID].discoverDescriptors(function(error, descriptors) { 212 | var clientCharacteristicConfigurationDescriptor = null; 213 | 214 | for (var i in descriptors) { 215 | var descriptor = descriptors[i]; 216 | 217 | if (descriptor.uuid === '2902') { 218 | clientCharacteristicConfigurationDescriptor = descriptor; 219 | break; 220 | } 221 | } 222 | 223 | clientCharacteristicConfigurationDescriptor.writeValue(new Buffer('0000', 'hex'), function(error) { 224 | this.writeAdminPassword(password, function(result) { 225 | callback(result === 0); 226 | }.bind(this)); 227 | }.bind(this)); 228 | }.bind(this)); 229 | }; 230 | 231 | BleuStation.prototype.readDeviceName = function(callback) { 232 | this.readStringCharacteristic(DEVICE_NAME_UUID, callback); 233 | }; 234 | 235 | BleuStation.prototype.readManufacturerName = function(callback) { 236 | this.readStringCharacteristic(MANUFACTURER_NAME_UUID, callback); 237 | }; 238 | 239 | BleuStation.prototype.readModelNumber = function(callback) { 240 | this.readStringCharacteristic(MODEL_NUMBER_UUID, callback); 241 | }; 242 | 243 | BleuStation.prototype.readHardwareRevision = function(callback) { 244 | this.readStringCharacteristic(HARDWARE_REVISION_UUID, callback); 245 | }; 246 | 247 | BleuStation.prototype.readFirmwareRevision = function(callback) { 248 | this.readStringCharacteristic(FIRMWARE_REVISION_UUID, callback); 249 | }; 250 | 251 | BleuStation.prototype.readUuid = function(callback) { 252 | this.readDataCharacteristic(IBEACON_UUID_UUID, function(data) { 253 | callback(data.toString('hex')); 254 | }); 255 | }; 256 | 257 | BleuStation.prototype.readMajor = function(callback) { 258 | this.readUInt16Characteristic(IBEACON_MAJOR_UUID, callback); 259 | }; 260 | 261 | BleuStation.prototype.readMinor = function(callback) { 262 | this.readUInt16Characteristic(IBEACON_MINOR_UUID, callback); 263 | }; 264 | 265 | BleuStation.prototype.readTxPower = function(callback) { 266 | this.readUInt8Characteristic(ADMIN_TX_POWER_UUID, function(value) { 267 | var txPower = value * 5 + 25; 268 | 269 | callback(txPower); 270 | }.bind(this)); 271 | }; 272 | 273 | BleuStation.prototype.readMeasuredTxPower = function(callback) { 274 | this.readInt8Characteristic(IBEACON_MEASURED_TX_POWER_UUID, callback); 275 | }; 276 | 277 | BleuStation.prototype.readAdminDeviceName = function(callback) { 278 | this.readStringCharacteristic(ADMIN_DEVICE_NAME_UUID, callback); 279 | }; 280 | 281 | BleuStation.prototype.writeUuid = function(uuid, callback) { 282 | this.writeDataCharacteristic(ADMIN_IBEACON_UUID_UUID, new Buffer(uuid, 'hex'), callback); 283 | }; 284 | 285 | BleuStation.prototype.writeMajor = function(major, callback) { 286 | this.writeUInt16Characteristic(ADMIN_IBEACON_MAJOR_UUID, major, callback); 287 | }; 288 | 289 | BleuStation.prototype.writeMinor = function(minor, callback) { 290 | this.writeUInt16Characteristic(ADMIN_IBEACON_MINOR_UUID, minor, callback); 291 | }; 292 | 293 | BleuStation.prototype.writeTxPower = function(txPower, callback) { 294 | if (txPower > 100) { 295 | txPower = 100; 296 | } else if (txPower < 25) { 297 | txPower = 25; 298 | } 299 | 300 | var value = Math.ceil((txPower - 25) / 5); 301 | 302 | this.writeUInt8Characteristic(ADMIN_TX_POWER_UUID, value, callback); 303 | }; 304 | 305 | BleuStation.prototype.writeMeasuredTxPower = function(measureTxPower, callback) { 306 | this.writeInt8Characteristic(ADMIN_TX_POWER_CAL_UUID, measureTxPower, callback); 307 | }; 308 | 309 | BleuStation.prototype.writeAdminDeviceName = function(deviceName, callback) { 310 | this.writeStringCharacteristic(ADMIN_DEVICE_NAME_UUID, deviceName, callback); 311 | }; 312 | 313 | BleuStation.prototype.writeAdminPassword = function(password, callback) { 314 | this.writeStringCharacteristic(ADMIN_PASSWORD_UUID, password, function() { 315 | this.readUInt8Characteristic(ADMIN_PASSWORD_UUID, callback); 316 | }.bind(this)); 317 | }; 318 | 319 | BleuStation.prototype.readLatitude = function(callback) { 320 | this.readDoubleCharacteristic(LATITUDE_UUID, callback); 321 | }; 322 | 323 | BleuStation.prototype.writeLatitude = function(latitude, callback) { 324 | this.writeDoubleCharacteristic(LATITUDE_UUID, latitude, callback); 325 | }; 326 | 327 | BleuStation.prototype.readLongitude = function(callback) { 328 | this.readDoubleCharacteristic(LONGITUDE_UUID, callback); 329 | }; 330 | 331 | BleuStation.prototype.writeLongitude = function(longitude, callback) { 332 | this.writeDoubleCharacteristic(LONGITUDE_UUID, longitude, callback); 333 | }; 334 | 335 | BleuStation.prototype.readVersionNumber = function(callback) { 336 | this.readInt8Characteristic(VERSION_NUMBER_UUID, callback); 337 | }; 338 | 339 | BleuStation.prototype.readURL1 = function(callback) { 340 | this.readStringCharacteristic(URL_1_UUID, callback); 341 | }; 342 | 343 | BleuStation.prototype.writeURL1 = function(url1, callback) { 344 | this.writeStringCharacteristic(URL_1_UUID, url1, callback); 345 | }; 346 | 347 | BleuStation.prototype.readURL2 = function(callback) { 348 | this.readStringCharacteristic(URL_2_UUID, callback); 349 | }; 350 | 351 | BleuStation.prototype.writeURL2 = function(url2, callback) { 352 | this.writeStringCharacteristic(URL_2_UUID, url2, callback); 353 | }; 354 | 355 | module.exports = BleuStation; 356 | -------------------------------------------------------------------------------- /bleu-station/bleu-station.txt: -------------------------------------------------------------------------------- 1 | 00:07:80:71:E1:33 2 | 3 | peripheral discovered (fa926014578e4e4e9849d23259e6d457): 4 | hello my local name is: 5 | TC71E133 6 | can I interest you in any of the following advertised services: 7 | [] 8 | here is my manufacturer data: 9 | "4c000215e2c56db5dffb48d2b060d0f5a71096e000010002c4" 10 | 11 | 1800 12 | 2a00 read TC71E133 (Device Name) 13 | 2a01 read 0x4142 (Appearance) 14 | 15 | 180a 16 | 2a29 read TwoCanoes (Manufacturer Name String) 17 | 2a24 read iBeacon Demo (Model Number String) 18 | 2a27 read 0.0.0 (Hardware Revision) 19 | 2a26 read 0.0.0 (Firmware Revision) 20 | 21 | b0702880a295a8abf734031a98a512de 22 | 23 | b0702881a295a8abf734031a98a512de read e2c56db5dffb48d2b060d0f5a71096e0 (ibeacon uuid) 24 | b0702882a295a8abf734031a98a512de read 0001 (ibeacon major) 25 | b0702883a295a8abf734031a98a512de read 0002 (ibeacon minor) 26 | b0702884a295a8abf734031a98a512de read -60 (ibeacon measured tx power) 27 | 28 | 29 | 2e88378c47ee48a8bc1374ec0d4ab559 30 | c8f21a07078a42df86600946ffd109be read, write (TC admin uuid) 31 | 677ec16a743d42fcafe1d9f4a02a726f read, write (TC admin major) 32 | 7722712a07f4433f8e305a6dc26356ba read, write (TC admin minor) 33 | 8aa2414e8e614d9cae14508ee3192dee read, write 25 - 100% (TC admin tx power) 34 | 0b4700c35c5346519601b7e1e06b1bbf read, write (TC admin tx cal) 35 | 980ac81e94fd43f48b9a260a65dd3adc read, write (TC admin device name) 36 | 12d8cca8b1cc4e48abf7767b5e0f3ff6 read, write, indicate 0 - success, 1 - changed, 2 - incorrect(TC admin password) 37 | 38 | 0628d1540c244cf3a6f591446c38c1f4 39 | 40 | fffc3dbb92d148f1aa5289a3d9517d79 read, write 123.456789 (TC Latitude) 41 | 9e5f3adf337f4acfb54d929da486f512 read, write 999.123456 (TC Longitude) 42 | 676e5ff15cd7484ab98f97f3c96e6361 read (TC Version Number) 43 | 44 | 9a87fdf8108b4b6f840fba9927ec4d9e 45 | 98a5a965efd34d16924718fd88bb8a30 read, write https://twocanoes.com/bleu/config/default.plist (TC URL 1) 46 | 3e8501d8aa3943368908b6e79d81a050 read, write (TC URL 2) 47 | -------------------------------------------------------------------------------- /bleu-station/pseudo.js: -------------------------------------------------------------------------------- 1 | var os = require('os'); 2 | var util = require('util'); 3 | 4 | var bleno = require('bleno'); 5 | 6 | var BlenoPrimaryService = bleno.PrimaryService; 7 | var BlenoCharacteristic = bleno.Characteristic; 8 | var BlenoDescriptor = bleno.Descriptor; 9 | 10 | console.log('pseudo - bleu station'); 11 | 12 | bleno.on('stateChange', function(state) { 13 | console.log('on -> stateChange: ' + state); 14 | 15 | if (state === 'poweredOn') { 16 | bleno.startAdvertising('TC000000'); 17 | } else { 18 | bleno.stopAdvertising(); 19 | } 20 | }); 21 | 22 | bleno.on('advertisingStart', function(error) { 23 | console.log('on -> advertisingStart ' + error); 24 | 25 | if (!error) { 26 | bleno.setServices([ 27 | new BlenoPrimaryService({ 28 | uuid: '180a', 29 | characteristics: [ 30 | new BlenoCharacteristic({ 31 | uuid: '2a29', 32 | properties: ['read'], 33 | value: new Buffer('TwoCanoes') 34 | }), 35 | new BlenoCharacteristic({ 36 | uuid: '2a24', 37 | properties: ['read'], 38 | value: new Buffer('iBeacon Demo') 39 | }), 40 | new BlenoCharacteristic({ 41 | uuid: '2a27', 42 | properties: ['read'], 43 | value: new Buffer('0.0.0') 44 | }), 45 | new BlenoCharacteristic({ 46 | uuid: '2a26', 47 | properties: ['read'], 48 | value: new Buffer('0.0.0') 49 | }) 50 | ] 51 | }), 52 | new BlenoPrimaryService({ 53 | uuid: 'b0702880a295a8abf734031a98a512de', 54 | characteristics: [ 55 | new BlenoCharacteristic({ 56 | uuid: 'b0702881a295a8abf734031a98a512de', 57 | properties: ['read'], 58 | value: new Buffer('e2c56db5dffb48d2b060d0f5a71096e0', 'hex') 59 | }), 60 | new BlenoCharacteristic({ 61 | uuid: 'b0702882a295a8abf734031a98a512de', 62 | properties: ['read'], 63 | value: new Buffer('0001', 'hex') 64 | }), 65 | new BlenoCharacteristic({ 66 | uuid: 'b0702883a295a8abf734031a98a512de', 67 | properties: ['read'], 68 | value: new Buffer('0002', 'hex') 69 | }), 70 | new BlenoCharacteristic({ 71 | uuid: 'b0702884a295a8abf734031a98a512de', 72 | properties: ['read'], 73 | value: new Buffer('c4', 'hex') 74 | }) 75 | ] 76 | }), 77 | new BlenoPrimaryService({ 78 | uuid: '2141aae18f8249daae256d6914825018', 79 | characteristics: [ 80 | // dummy 81 | new BlenoCharacteristic({ 82 | uuid: '00000000000000000000000000000000', 83 | properties: ['read'], 84 | value: '' 85 | }) 86 | ] 87 | }), 88 | new BlenoPrimaryService({ 89 | uuid: '2e88378c47ee48a8bc1374ec0d4ab559', 90 | characteristics: [ 91 | new BlenoCharacteristic({ 92 | uuid: 'c8f21a07078a42df86600946ffd109be', 93 | properties: ['read', 'write'], 94 | onReadRequest: function(offset, callback) { 95 | console.log('c8f21a07078a42df86600946ffd109be onReadRequest'); 96 | 97 | callback(BlenoCharacteristic.RESULT_SUCCESS, new Buffer('e2c56db5dffb48d2b060d0f5a71096e0', 'hex')); 98 | }, 99 | onWriteRequest: function(data, offset, withoutResponse, callback) { 100 | console.log('c8f21a07078a42df86600946ffd109be onWriteRequest ' + data.toString('hex')); 101 | 102 | callback(BlenoCharacteristic.RESULT_SUCCESS); 103 | } 104 | }), 105 | new BlenoCharacteristic({ 106 | uuid: '677ec16a743d42fcafe1d9f4a02a726f', 107 | properties: ['read', 'write'], 108 | onReadRequest: function(offset, callback) { 109 | console.log('677ec16a743d42fcafe1d9f4a02a726f onReadRequest'); 110 | 111 | callback(BlenoCharacteristic.RESULT_SUCCESS, new Buffer('0001', 'hex')); 112 | }, 113 | onWriteRequest: function(data, offset, withoutResponse, callback) { 114 | console.log('677ec16a743d42fcafe1d9f4a02a726f onWriteRequest ' + data.toString('hex')); 115 | 116 | callback(BlenoCharacteristic.RESULT_SUCCESS); 117 | } 118 | }), 119 | new BlenoCharacteristic({ 120 | uuid: '7722712a07f4433f8e305a6dc26356ba', 121 | properties: ['read', 'write'], 122 | onReadRequest: function(offset, callback) { 123 | console.log('7722712a07f4433f8e305a6dc26356ba onReadRequest'); 124 | 125 | callback(BlenoCharacteristic.RESULT_SUCCESS, new Buffer('0002', 'hex')); 126 | }, 127 | onWriteRequest: function(data, offset, withoutResponse, callback) { 128 | console.log('7722712a07f4433f8e305a6dc26356ba onWriteRequest ' + data.toString('hex')); 129 | 130 | callback(BlenoCharacteristic.RESULT_SUCCESS); 131 | } 132 | }), 133 | new BlenoCharacteristic({ 134 | uuid: '8aa2414e8e614d9cae14508ee3192dee', 135 | properties: ['read', 'write'], 136 | onReadRequest: function(offset, callback) { 137 | console.log('8aa2414e8e614d9cae14508ee3192dee onReadRequest'); 138 | 139 | callback(BlenoCharacteristic.RESULT_SUCCESS, new Buffer('0f', 'hex')); 140 | }, 141 | onWriteRequest: function(data, offset, withoutResponse, callback) { 142 | console.log('8aa2414e8e614d9cae14508ee3192dee onWriteRequest ' + data.toString('hex')); 143 | 144 | callback(BlenoCharacteristic.RESULT_SUCCESS); 145 | } 146 | }), 147 | new BlenoCharacteristic({ 148 | uuid: '0b4700c35c5346519601b7e1e06b1bbf', 149 | properties: ['read', 'write'], 150 | onReadRequest: function(offset, callback) { 151 | console.log('0b4700c35c5346519601b7e1e06b1bbf onReadRequest'); 152 | 153 | callback(BlenoCharacteristic.RESULT_SUCCESS, new Buffer('c4', 'hex')); 154 | }, 155 | onWriteRequest: function(data, offset, withoutResponse, callback) { 156 | console.log('0b4700c35c5346519601b7e1e06b1bbf onWriteRequest ' + data.toString('hex')); 157 | 158 | callback(BlenoCharacteristic.RESULT_SUCCESS); 159 | } 160 | }), 161 | new BlenoCharacteristic({ 162 | uuid: '980ac81e94fd43f48b9a260a65dd3adc', 163 | properties: ['read', 'write'], 164 | onReadRequest: function(offset, callback) { 165 | console.log('980ac81e94fd43f48b9a260a65dd3adc onReadRequest'); 166 | 167 | callback(BlenoCharacteristic.RESULT_SUCCESS, new Buffer('TC000000')); 168 | }, 169 | onWriteRequest: function(data, offset, withoutResponse, callback) { 170 | console.log('980ac81e94fd43f48b9a260a65dd3adc onWriteRequest ' + data.toString('hex')); 171 | 172 | callback(BlenoCharacteristic.RESULT_SUCCESS); 173 | } 174 | }), 175 | new BlenoCharacteristic({ 176 | uuid: '12d8cca8b1cc4e48abf7767b5e0f3ff6', 177 | properties: ['read', 'write'], 178 | onReadRequest: function(offset, callback) { 179 | console.log('12d8cca8b1cc4e48abf7767b5e0f3ff6 onReadRequest'); 180 | 181 | callback(BlenoCharacteristic.RESULT_SUCCESS, new Buffer('', 'hex')); 182 | }, 183 | onWriteRequest: function(data, offset, withoutResponse, callback) { 184 | console.log('12d8cca8b1cc4e48abf7767b5e0f3ff6 onWriteRequest ' + data.toString('hex')); 185 | 186 | callback(BlenoCharacteristic.RESULT_SUCCESS); 187 | } 188 | }) 189 | ] 190 | }), 191 | new BlenoPrimaryService({ 192 | uuid: '0628d1540c244cf3a6f591446c38c1f4', 193 | characteristics: [ 194 | new BlenoCharacteristic({ 195 | uuid: 'fffc3dbb92d148f1aa5289a3d9517d79', 196 | properties: ['read', 'write'], 197 | onReadRequest: function(offset, callback) { 198 | console.log('fffc3dbb92d148f1aa5289a3d9517d79 onReadRequest'); 199 | 200 | var data = new Buffer(8); 201 | data.writeDoubleLE(123.456789, 0); 202 | 203 | callback(BlenoCharacteristic.RESULT_SUCCESS, data); 204 | }, 205 | onWriteRequest: function(data, offset, withoutResponse, callback) { 206 | console.log('fffc3dbb92d148f1aa5289a3d9517d79 onWriteRequest ' + data.readDoubleLE(0)); 207 | 208 | callback(BlenoCharacteristic.RESULT_SUCCESS); 209 | } 210 | }), 211 | new BlenoCharacteristic({ 212 | uuid: '9e5f3adf337f4acfb54d929da486f512', 213 | properties: ['read', 'write'], 214 | onReadRequest: function(offset, callback) { 215 | console.log('9e5f3adf337f4acfb54d929da486f512 onReadRequest'); 216 | 217 | var data = new Buffer(8); 218 | data.writeDoubleLE(999.123456, 0); 219 | 220 | callback(BlenoCharacteristic.RESULT_SUCCESS, data); 221 | }, 222 | onWriteRequest: function(data, offset, withoutResponse, callback) { 223 | console.log('9e5f3adf337f4acfb54d929da486f512 onWriteRequest ' + data.readDoubleLE(0)); 224 | 225 | callback(BlenoCharacteristic.RESULT_SUCCESS); 226 | } 227 | }), 228 | new BlenoCharacteristic({ 229 | uuid: '676e5ff15cd7484ab98f97f3c96e6361', 230 | properties: ['read', 'write'], 231 | onReadRequest: function(offset, callback) { 232 | console.log('676e5ff15cd7484ab98f97f3c96e6361 onReadRequest'); 233 | 234 | var data = new Buffer([1]); 235 | 236 | callback(BlenoCharacteristic.RESULT_SUCCESS, data); 237 | }, 238 | onWriteRequest: function(data, offset, withoutResponse, callback) { 239 | console.log('676e5ff15cd7484ab98f97f3c96e6361 onWriteRequest ' + data.readDoubleLE(0)); 240 | 241 | callback(BlenoCharacteristic.RESULT_SUCCESS); 242 | } 243 | }) 244 | ] 245 | }), 246 | new BlenoPrimaryService({ 247 | uuid: '9a87fdf8108b4b6f840fba9927ec4d9e', 248 | characteristics: [ 249 | new BlenoCharacteristic({ 250 | uuid: '98a5a965efd34d16924718fd88bb8a30', 251 | properties: ['read', 'write'], 252 | onReadRequest: function(offset, callback) { 253 | console.log('98a5a965efd34d16924718fd88bb8a30 onReadRequest'); 254 | 255 | var data = new Buffer('/https://twocanoes.com/bleu/config/default.plist'); 256 | 257 | callback(BlenoCharacteristic.RESULT_SUCCESS, data); 258 | }, 259 | onWriteRequest: function(data, offset, withoutResponse, callback) { 260 | console.log('98a5a965efd34d16924718fd88bb8a30 onWriteRequest ' + data.readDoubleLE(0)); 261 | 262 | callback(BlenoCharacteristic.RESULT_SUCCESS); 263 | } 264 | }), 265 | new BlenoCharacteristic({ 266 | uuid: '3e8501d8aa3943368908b6e79d81a050', 267 | properties: ['read', 'write'], 268 | onReadRequest: function(offset, callback) { 269 | console.log('3e8501d8aa3943368908b6e79d81a050 onReadRequest'); 270 | 271 | var data = new Buffer(''); 272 | 273 | callback(BlenoCharacteristic.RESULT_SUCCESS, data); 274 | }, 275 | onWriteRequest: function(data, offset, withoutResponse, callback) { 276 | console.log('3e8501d8aa3943368908b6e79d81a050 onWriteRequest ' + data.readDoubleLE(0)); 277 | 278 | callback(BlenoCharacteristic.RESULT_SUCCESS); 279 | } 280 | }) 281 | ] 282 | }) 283 | ]); 284 | } 285 | }); 286 | 287 | bleno.on('advertisingStop', function() { 288 | console.log('on -> advertisingStop'); 289 | }); 290 | 291 | bleno.on('servicesSet', function() { 292 | console.log('on -> servicesSet'); 293 | }); 294 | -------------------------------------------------------------------------------- /bleu-station/test.js: -------------------------------------------------------------------------------- 1 | var async = require('async'); 2 | 3 | var BleuStation = require('./bleu-station.js'); 4 | 5 | var READ_ONLY = false; 6 | 7 | BleuStation.discover(function(bleuStation) { 8 | async.series([ 9 | function(callback) { 10 | bleuStation.on('disconnect', function() { 11 | console.log('disconnected!'); 12 | process.exit(0); 13 | }); 14 | 15 | console.log('found: ' + bleuStation.toString()); 16 | 17 | console.log('connect'); 18 | bleuStation.connect(callback); 19 | }, 20 | function(callback) { 21 | console.log('discoverServicesAndCharacteristics'); 22 | bleuStation.discoverServicesAndCharacteristics(callback); 23 | }, 24 | function(callback) { 25 | if (READ_ONLY) { 26 | callback(); 27 | } else { 28 | console.log('login'); 29 | bleuStation.login('qwerty123', function(result) { 30 | console.log('\tsuccess = ' + result); 31 | 32 | if (result) { 33 | callback(); 34 | } else { 35 | bleuStation.disconnect(); 36 | } 37 | }); 38 | } 39 | }, 40 | function(callback) { 41 | console.log('readDeviceName'); 42 | bleuStation.readDeviceName(function(deviceName) { 43 | console.log('\tdevice name = ' + deviceName); 44 | 45 | callback(); 46 | }); 47 | }, 48 | function(callback) { 49 | console.log('readManufacturerName'); 50 | bleuStation.readManufacturerName(function(manufacturerName) { 51 | console.log('\tmanufacturer name = ' + manufacturerName); 52 | callback(); 53 | }); 54 | }, 55 | function(callback) { 56 | console.log('readModelNumber'); 57 | bleuStation.readModelNumber(function(modelNumber) { 58 | console.log('\tmodel number = ' + modelNumber); 59 | callback(); 60 | }); 61 | }, 62 | function(callback) { 63 | console.log('readHardwareRevision'); 64 | bleuStation.readHardwareRevision(function(hardwareRevision) { 65 | console.log('\thardware revision = ' + hardwareRevision); 66 | callback(); 67 | }); 68 | }, 69 | function(callback) { 70 | console.log('readFirmwareRevision'); 71 | bleuStation.readFirmwareRevision(function(firmwareRevision) { 72 | console.log('\tfirmware revision = ' + firmwareRevision); 73 | callback(); 74 | }); 75 | }, 76 | function(callback) { 77 | console.log('readUuid'); 78 | bleuStation.readUuid(function(uuid) { 79 | console.log('\tuuid = ' + uuid); 80 | 81 | if (READ_ONLY) { 82 | callback(); 83 | } else { 84 | console.log('writeUuid'); 85 | uuid = 'e2c56db5dffb48d2b060d0f5a71096e0'; 86 | bleuStation.writeUuid(uuid, callback); 87 | } 88 | }); 89 | }, 90 | function(callback) { 91 | console.log('readMajor'); 92 | bleuStation.readMajor(function(major) { 93 | console.log('\tmajor = ' + major + ' (0x' + major.toString(16) + ')'); 94 | 95 | if (READ_ONLY) { 96 | callback(); 97 | } else { 98 | console.log('writeMajor'); 99 | major = 1; 100 | bleuStation.writeMajor(major, callback); 101 | } 102 | }); 103 | }, 104 | function(callback) { 105 | console.log('readMinor'); 106 | bleuStation.readMinor(function(minor) { 107 | console.log('\tminor = ' + minor + ' (0x' + minor.toString(16) + ')'); 108 | 109 | if (READ_ONLY) { 110 | callback(); 111 | } else { 112 | console.log('writeMinor'); 113 | minor = 2; 114 | bleuStation.writeMinor(minor, callback); 115 | } 116 | }); 117 | }, 118 | function(callback) { 119 | console.log('readTxPower'); 120 | bleuStation.readTxPower(function(txPower) { 121 | console.log('\tTX power = ' + txPower); 122 | 123 | if (READ_ONLY) { 124 | callback(); 125 | } else { 126 | console.log('writeTxPower'); 127 | txPower = 100; 128 | bleuStation.writeTxPower(txPower, callback); 129 | } 130 | }); 131 | }, 132 | function(callback) { 133 | console.log('readMeasuredTxPower'); 134 | bleuStation.readMeasuredTxPower(function(measuredTxPower) { 135 | console.log('\tmeasured TX Power = ' + measuredTxPower); 136 | 137 | if (READ_ONLY) { 138 | callback(); 139 | } else { 140 | console.log('writeMeasuredTxPower'); 141 | measuredTxPower = -60; 142 | bleuStation.writeMeasuredTxPower(measuredTxPower, callback); 143 | } 144 | }); 145 | }, 146 | function(callback) { 147 | console.log('readAdminDeviceName'); 148 | bleuStation.readAdminDeviceName(function(adminDeviceName) { 149 | console.log('\tadmin device name = ' + adminDeviceName); 150 | 151 | if (READ_ONLY) { 152 | callback(); 153 | } else { 154 | console.log('writeAdminDeviceName'); 155 | adminDeviceName = ''; 156 | bleuStation.writeAdminDeviceName(adminDeviceName, callback); 157 | } 158 | }); 159 | }, 160 | function(callback) { 161 | console.log('readLatitude'); 162 | bleuStation.readLatitude(function(latitude) { 163 | console.log('\tlatitude = ' + latitude); 164 | 165 | if (READ_ONLY) { 166 | callback(); 167 | } else { 168 | console.log('writeLatitude'); 169 | latitude = 123.456789; 170 | bleuStation.writeLatitude(latitude, callback); 171 | } 172 | }); 173 | }, 174 | function(callback) { 175 | console.log('readLongitude'); 176 | bleuStation.readLongitude(function(longitude) { 177 | console.log('\tlongitude = ' + longitude); 178 | 179 | if (READ_ONLY) { 180 | callback(); 181 | } else { 182 | console.log('writeLongitude'); 183 | longitude = 999.123456; 184 | bleuStation.writeLongitude(longitude, callback); 185 | } 186 | }); 187 | }, 188 | function(callback) { 189 | console.log('readVersionNumber'); 190 | bleuStation.readVersionNumber(function(versionNumber) { 191 | console.log('\tversion number = ' + versionNumber); 192 | 193 | callback(); 194 | }); 195 | }, 196 | function(callback) { 197 | console.log('readURL1'); 198 | bleuStation.readURL1(function(url1) { 199 | console.log('\turl 1 = ' + url1); 200 | 201 | if (READ_ONLY) { 202 | callback(); 203 | } else { 204 | console.log('writeURL1'); 205 | url1 = 'https://twocanoes.com/bleu/config/default.plist'; 206 | bleuStation.writeURL1(url1, callback); 207 | } 208 | }); 209 | }, 210 | function(callback) { 211 | console.log('readURL2'); 212 | bleuStation.readURL2(function(url2) { 213 | console.log('\turl 2 = ' + url2); 214 | 215 | if (READ_ONLY) { 216 | callback(); 217 | } else { 218 | console.log('writeURL2'); 219 | url2 = ''; 220 | bleuStation.writeURL2(url2, callback); 221 | } 222 | }); 223 | }, 224 | function(callback) { 225 | console.log('disconnect'); 226 | bleuStation.disconnect(callback); 227 | } 228 | ]); 229 | }); 230 | -------------------------------------------------------------------------------- /estimote-sticker/estimote-sticker.js: -------------------------------------------------------------------------------- 1 | var events = require('events'); 2 | var os = require('os'); 3 | var util = require('util'); 4 | 5 | var debug = require('debug')('estimote-sticker'); 6 | 7 | var noble = require('noble'); 8 | 9 | if(process.env.NOBLE_REPORT_ALL_HCI_EVENTS === undefined && os.platform() !== 'darwin') { 10 | debug("NOBLE_REPORT_ALL_HCI_EVENTS env variable has to be set to 1"); 11 | process.env.NOBLE_REPORT_ALL_HCI_EVENTS = 1; 12 | } 13 | 14 | var EstimoteSticker = function() { 15 | noble.on('discover', this.onDiscover.bind(this)); 16 | }; 17 | 18 | var EXPECTED_MANUFACTURER_DATA_LENGTH = 22; 19 | 20 | util.inherits(EstimoteSticker, events.EventEmitter); 21 | 22 | 23 | EstimoteSticker.prototype.startScanning = function() { 24 | debug('startScanning'); 25 | 26 | function startScanning() { 27 | noble.startScanning([], true); 28 | } 29 | 30 | if (noble.state === 'poweredOn') { 31 | startScanning(); 32 | } else { 33 | noble.once('stateChange', startScanning); 34 | } 35 | }; 36 | 37 | EstimoteSticker.prototype.stopScanning = function() { 38 | debug('stopScanning'); 39 | noble.stopScanning(); 40 | }; 41 | 42 | EstimoteSticker.prototype.onDiscover = function(peripheral) { 43 | debug('onDiscover: %s', peripheral); 44 | 45 | var manufacturerData = peripheral.advertisement.manufacturerData; 46 | var rssi = peripheral.rssi; 47 | 48 | debug('onDiscover: manufacturerData = %s, rssi = %d', manufacturerData ? manufacturerData.toString('hex') : null, rssi); 49 | 50 | if (manufacturerData && 51 | EXPECTED_MANUFACTURER_DATA_LENGTH <= manufacturerData.length && 52 | manufacturerData[0] === 0x5d && manufacturerData[1] === 0x01 && // company id 53 | manufacturerData[2] === 0x01) { // nearable protocol version 54 | 55 | debug('onDiscover: ' + peripheral.uuid + ' ' + manufacturerData.toString('hex')); 56 | 57 | // id can be looked up: https://cloud.estimote.com/v1/stickers//info 58 | // response: {"identifier":"","type":"shoe","color":"blueberry","category":"shoe"} 59 | var id = manufacturerData.slice(3, 11).toString('hex'); 60 | var major = parseInt(manufacturerData.slice(7, 9).toString('hex'), 16); 61 | var minor = parseInt(manufacturerData.slice(9, 11).toString('hex'), 16); 62 | var uuid = 'd0d3fa86ca7645ec9bd96af4' + manufacturerData.slice(3, 7).toString('hex'); 63 | var type = (manufacturerData[11] === 0x4) ? 'SB0' : 'unknown'; 64 | var firmware = null; 65 | 66 | switch (manufacturerData[12]) { 67 | case -127: 68 | firmware = 'SA1.0.0'; 69 | break; 70 | 71 | case -126: 72 | firmware = 'SA1.0.1'; 73 | break; 74 | 75 | default: 76 | firmware = 'unknown'; 77 | break; 78 | } 79 | 80 | var bootloader = (manufacturerData[12] === 0x1) ? 'SB1.0.0' : 'unknown'; 81 | 82 | var rawTemperature = (manufacturerData.readUInt16LE(13) & 0x0fff) << 4; 83 | var temperature = null; 84 | 85 | if (rawTemperature & 0x8000) { 86 | temperature = ((rawTemperature & 0x7fff) - 32768.0) / 256.0; 87 | } else { 88 | temperature = rawTemperature / 256.0; 89 | } 90 | 91 | var moving = (manufacturerData.readUInt8(15) & 0x40) !== 0; 92 | 93 | var batteryVoltage = 0; 94 | 95 | if ((manufacturerData.readUInt8(15) & 0x80) === 0) { 96 | var rawBatteryLevel = (manufacturerData.readUInt8(15) << 8) + ((manufacturerData.readUInt8(14) >> 4) & 0x3ff); 97 | 98 | batteryVoltage = 3.6 * rawBatteryLevel / 1023.0; 99 | } 100 | 101 | var batteryLevel = 'unknown'; 102 | 103 | if (batteryVoltage >= 2.95) { 104 | batteryLevel = 'high'; 105 | } else if (batteryVoltage < 2.95 && batteryVoltage >= 2.7) { 106 | batteryLevel = 'medium'; 107 | } else if (batteryVoltage > 0.0) { 108 | batteryLevel = 'low'; 109 | } 110 | 111 | var acceleration = { 112 | x: manufacturerData.readInt8(16) * 15.625, 113 | y: manufacturerData.readInt8(17) * 15.625, 114 | z: manufacturerData.readInt8(18) * 15.625 115 | }; 116 | 117 | var currentMotionStateDuration = convertMotionStateDuration(manufacturerData.readUInt8(19)); 118 | var previousMotionStateDuration = convertMotionStateDuration(manufacturerData.readUInt8(20)); 119 | 120 | var power = [1, 2, 3, 7, 5, 6, 4, 8][manufacturerData.readUInt8(21) & 0x0f] || 'unknown'; 121 | var firmwareState = (manufacturerData.readUInt8(21) & 0x40) ? 'app' : 'bootloader'; 122 | 123 | var sticker = { 124 | id: id, 125 | uuid: uuid, 126 | major: major, 127 | minor: minor, 128 | type: type, 129 | firmware: firmware, 130 | bootloader: bootloader, 131 | temperature: temperature, 132 | moving: moving, 133 | batteryLevel: batteryLevel, 134 | acceleration: acceleration, 135 | currentMotionStateDuration: currentMotionStateDuration, 136 | previousMotionStateDuration: previousMotionStateDuration, 137 | power: power, 138 | firmwareState: firmwareState, 139 | rssi: rssi 140 | }; 141 | 142 | this.emit('discover', sticker); 143 | } 144 | }; 145 | 146 | function convertMotionStateDuration(raw) { 147 | var unit = (raw >> 6) & 0x03; 148 | var duration = (raw & 0x3f); 149 | 150 | if (unit === 1) { 151 | duration *= 60; 152 | } else if (unit === 2) { 153 | duration *= (60 * 60); 154 | } 155 | 156 | return duration; 157 | } 158 | 159 | module.exports = new EstimoteSticker(); 160 | -------------------------------------------------------------------------------- /estimote-sticker/pseudo.js: -------------------------------------------------------------------------------- 1 | var os = require('os'); 2 | 3 | var bleno = require('bleno'); 4 | 5 | var platform = os.platform(); 6 | 7 | console.log('pseudo - estimote sticker'); 8 | 9 | bleno.on('stateChange', function(state) { 10 | console.log('on -> stateChange: ' + state); 11 | 12 | if (state === 'poweredOn') { 13 | var advertisement = Buffer.concat([ 14 | new Buffer('03030f18', 'hex'), 15 | new Buffer('17ff', 'hex'), 16 | new Buffer('5d0101', 'hex'), 17 | new Buffer('0398290ef72bcff9', 'hex'), 18 | 19 | new Buffer('0401', 'hex') , 20 | new Buffer('000000000000000000', 'hex') 21 | ]); 22 | 23 | if (platform === 'darwin') { 24 | bleno.startAdvertisingWithEIRData(advertisement); 25 | } else if (platform === 'linux') { 26 | var scan = new Buffer(0); 27 | bleno.startAdvertisingWithEIRData(advertisement, scan); 28 | } 29 | } else { 30 | bleno.stopAdvertising(); 31 | } 32 | }); 33 | 34 | bleno.on('advertisingStart', function() { 35 | console.log('on -> advertisingStart'); 36 | }); 37 | 38 | bleno.on('advertisingStop', function() { 39 | console.log('on -> advertisingStop'); 40 | }); 41 | -------------------------------------------------------------------------------- /estimote-sticker/test.js: -------------------------------------------------------------------------------- 1 | var EstimoteSticker = require('./estimote-sticker'); 2 | 3 | EstimoteSticker.on('discover', function(estimoteSticker) { 4 | console.log(estimoteSticker); 5 | }); 6 | 7 | EstimoteSticker.startScanning(); 8 | -------------------------------------------------------------------------------- /estimote/README.md: -------------------------------------------------------------------------------- 1 | bleacon - Estimote 2 | ================== 3 | 4 | Configure [Estimote](http://estimote.com) iBeacons. 5 | 6 | **Warning** not compatible with firmware versions 3.2.0 or higher. See [Estimote Fixes Security Problems with Beacon Firmware](http://makezine.com/2015/08/28/estimote-fixes-security-problems-with-beacon-firmware/) for more info. 7 | 8 | Usage 9 | ----- 10 | 11 | ```javascript 12 | var Estimote = require('bleacon').Estimote; 13 | ``` 14 | 15 | __Discover__ 16 | 17 | ```javascript 18 | Estimote.discover(callback(estimote)); 19 | 20 | Estimote.discoverAll(callback(estimote)); 21 | ``` 22 | 23 | __Connect an Setup__ 24 | 25 | Run after discover. 26 | 27 | ```javascript 28 | estimote.connectAndSetUp(callback(error)); 29 | ``` 30 | 31 | __Disconnect__ 32 | 33 | ```javascript 34 | estimote.disconnect(callback(error)); 35 | ``` 36 | 37 | 38 | __Pair__ 39 | 40 | Run after ```connectAndSetUp``` prior to write operations. 41 | 42 | ```javascript 43 | estimote.pair(callback(error)); 44 | ``` 45 | 46 | __Device Info__ 47 | 48 | ```javascript 49 | estimote.readDeviceName(callback(error, deviceName)); 50 | 51 | var deviceName = 'estimote'; 52 | estimote.writeDeviceName(deviceName, callback(error)); 53 | 54 | estimote.readBatteryLevel(callback(error, batteryLevel)); 55 | 56 | estimote.readFirmwareRevision(callback(error, firmwareRevision)); 57 | 58 | estimote.readHardwareRevision(callback(error, hardwareRevision)); 59 | ``` 60 | 61 | __iBeacon__ 62 | 63 | ```javascript 64 | // UUID 1 & 2 65 | var uuid = 'b9407f30f5f8466eaff925556b57fe6d'; 66 | 67 | estimote.readUuid1(callback(error, uuid1)); 68 | estimote.readUuid2(callback(error, uuid2)); 69 | 70 | estimote.writeUuid1(uuid, callback(error)); 71 | estimote.writeUuid2(uuid, callback(error)); 72 | 73 | // Major 74 | estimote.readMajor(callback(error, major)); 75 | 76 | var major = 0x0001; // 0 - 65535 77 | estimote.writeMajor(major, callback(error)); 78 | 79 | // Minor 80 | estimote.readMinor(callback(error, minor)); 81 | 82 | var minor = 0x0002; // 0 - 65535 83 | estimote.writeMinor(minor, callback(error)); 84 | ``` 85 | 86 | __Other__ 87 | 88 | ```javascript 89 | // Advertisement Interval 90 | estimote.readAdvertisementInterval(callback(error, advertisementInterval)); 91 | 92 | var advertisementInterval = 200; // 50 - 2000 ms 93 | estimote.writeAdvertisementInterval(advertisementInterval, callback(error)); 94 | 95 | // Power Level 96 | estimote.readPowerLevel(callback(error, powerLevel, dBm)); 97 | 98 | var powerLevel = 7; // 1 - 7 99 | estimote.writePowerLevel(powerLevel, callback(error)); 100 | ``` 101 | 102 | __Sensors__ 103 | 104 | ```javascript 105 | estimote.readTemperature(callback(error, temperature)); 106 | 107 | estimote.subscribeMotion(callback(error)); 108 | estimote.unsubscribeMotion(callback(error)); 109 | estimote.on('motionStateChange', callback(isMoving)); 110 | ``` 111 | 112 | Events 113 | ------ 114 | 115 | __Disconnect__ 116 | 117 | ```javascript 118 | estimote.on('disconnect', callback); 119 | ``` 120 | -------------------------------------------------------------------------------- /estimote/estimote.js: -------------------------------------------------------------------------------- 1 | var crypto = require('crypto'); 2 | 3 | var bignum = require('bignum'); 4 | var debug = require('debug')('estimote'); 5 | 6 | var NobleDevice = require('noble-device'); 7 | 8 | var GENERIC_ACCESS_SERVICE_UUID = '1800'; 9 | var DEVICE_NAME_UUID = '2a00'; 10 | 11 | var ESTIMOTE_SERVICE_UUID = 'b9403000f5f8466eaff925556b57fe6d'; 12 | var MAJOR_UUID = 'b9403001f5f8466eaff925556b57fe6d'; 13 | var MINOR_UUID = 'b9403002f5f8466eaff925556b57fe6d'; 14 | var UUID_1_UUID = 'b9403003f5f8466eaff925556b57fe6d'; 15 | var UUID_2_UUID = 'b9403004f5f8466eaff925556b57fe6d'; 16 | var POWER_LEVEL_UUID = 'b9403011f5f8466eaff925556b57fe6d'; 17 | var ADVERTISEMENT_INTERVAL_UUID = 'b9403012f5f8466eaff925556b57fe6d'; 18 | var TEMPERATURE_UUID = 'b9403021f5f8466eaff925556b57fe6d'; 19 | var MOTION_UUID = 'b9403031f5f8466eaff925556b57fe6d'; 20 | var SERVICE_2_09_UUID = 'b9403032f5f8466eaff925556b57fe6d'; 21 | var SERVICE_2_10_UUID = 'b9403051f5f8466eaff925556b57fe6d'; 22 | var BATTERY_LEVEL_UUID = 'b9403041f5f8466eaff925556b57fe6d'; 23 | var SERVICE_CONFIGURATION_UUID = 'b9403051f5f8466eaff925556b57fe6d'; 24 | var EDDYSTONE_UID_NAMESPACE_UUID = 'b9403071f5f8466eaff925556b57fe6d'; 25 | var EDDYSTONE_UID_INSTANCE_UUID = 'b9403072f5f8466eaff925556b57fe6d'; 26 | var EDDYSTONE_URL_UUID = 'b9403073f5f8466eaff925556b57fe6d'; 27 | 28 | var AUTH_SERVICE_UUID = 'b9402000f5f8466eaff925556b57fe6d'; 29 | var AUTH_SERVICE_1_UUID = 'b9402001f5f8466eaff925556b57fe6d'; // auth seed 30 | var AUTH_SERVICE_2_UUID = 'b9402002f5f8466eaff925556b57fe6d'; // auth vector 31 | 32 | var VERSION_SERVICE_UUID = 'b9404000f5f8466eaff925556b57fe6d'; 33 | var FIRMWARE_VERSION_UUID = 'b9404001f5f8466eaff925556b57fe6d'; 34 | var HARDWARE_VERSION_UUID = 'b9404002f5f8466eaff925556b57fe6d'; 35 | 36 | var Estimote = function(peripheral) { 37 | NobleDevice.call(this, peripheral); 38 | 39 | this.manufacturerData = (peripheral.advertisement.manufacturerData ? peripheral.advertisement.manufacturerData.toString('hex') : null); 40 | 41 | var serviceData = peripheral.advertisement.serviceData[0].data; 42 | 43 | this.address = serviceData.slice(0, 6).toString('hex').match(/.{1,2}/g).reverse().join(':'); 44 | this.addressData = new Buffer(this.address.split(':').join(''), 'hex'); 45 | 46 | this.measuredPower = serviceData.readInt8(6); 47 | this.major = serviceData.readUInt16LE(7); 48 | this.minor = serviceData.readUInt16LE(9); 49 | 50 | this._peripheral.on('disconnect', this.onDisconnect.bind(this)); 51 | this._onMotionDataBinded = this.onMotionData.bind(this); 52 | }; 53 | 54 | NobleDevice.Util.inherits(Estimote, NobleDevice); 55 | 56 | Estimote.SCAN_DUPLICATES = true; 57 | 58 | Estimote.is = function(peripheral) { 59 | var localName = peripheral.advertisement.localName; 60 | 61 | return ( (localName === 'estimote' || localName === 'EST') && // original || "new" name 62 | peripheral.advertisement.serviceData !== undefined && 63 | peripheral.advertisement.serviceData.length && 64 | peripheral.advertisement.serviceData[0].uuid === '180a'); 65 | }; 66 | 67 | Estimote.prototype.toString = function() { 68 | return JSON.stringify({ 69 | uuid: this.uuid, 70 | address: this.address, 71 | manufacturerData: this.manufacturerData, 72 | major: this.major, 73 | minor: this.minor, 74 | measuredPower: this.measuredPower 75 | }); 76 | }; 77 | 78 | 79 | Estimote.prototype.pair = function(callback) { 80 | var base = 5; 81 | var exp = Math.round(Math.random() * 0xffffffff); 82 | var mod = 0xfffffffb; 83 | 84 | var sec = bignum(base).powm(exp, mod); 85 | 86 | this.writeAuthService1(sec, function(error) { 87 | if (error) { 88 | return callback(error); 89 | } 90 | 91 | this.readAuthService1(function(error, authService1Value) { 92 | if (error) { 93 | return callback(error); 94 | } 95 | 96 | 97 | sec = bignum(authService1Value).powm(exp, mod); 98 | 99 | var authService2Data = new Buffer(16); 100 | 101 | // fill in authService2Data with address 102 | authService2Data[0] = this.addressData[5]; 103 | authService2Data[1] = this.addressData[4]; 104 | authService2Data[2] = this.addressData[3]; 105 | authService2Data[3] = this.addressData[2]; 106 | authService2Data[4] = this.addressData[1]; 107 | authService2Data[5] = this.addressData[0]; 108 | authService2Data[6] = this.addressData[3]; 109 | authService2Data[7] = this.addressData[4]; 110 | authService2Data[8] = this.addressData[5]; 111 | authService2Data[9] = this.addressData[0]; 112 | authService2Data[10] = this.addressData[1]; 113 | authService2Data[11] = this.addressData[2]; 114 | authService2Data[12] = this.addressData[1]; 115 | authService2Data[13] = this.addressData[3]; 116 | authService2Data[14] = this.addressData[2]; 117 | authService2Data[15] = this.addressData[4]; 118 | 119 | 120 | // encrypt 121 | var fixedKeyHexString = (this._peripheral.advertisement.localName === 'EST') ? 122 | 'c54fc29163e4457b8a9ac9868e1b3a9a' : // "new" fixed key (v3) 123 | 'ff8af207013625c2d810097f20d3050f'; // original fixed key 124 | 125 | var key = new Buffer(fixedKeyHexString, 'hex'); 126 | var iv = new Buffer('00000000000000000000000000000000', 'hex'); 127 | 128 | var cipher = crypto.createCipheriv('aes128', key, iv); 129 | 130 | cipher.setAutoPadding(false); 131 | authService2Data = cipher.update(authService2Data); 132 | 133 | // fill in key with sec 134 | var secData = new Buffer(4); 135 | secData.writeUInt32BE(sec, 0); 136 | 137 | key[0] = secData[3]; 138 | key[1] = secData[2]; 139 | key[2] = secData[1]; 140 | key[3] = secData[0]; 141 | key[4] = secData[0]; 142 | key[5] = secData[1]; 143 | key[6] = secData[2]; 144 | key[7] = secData[3]; 145 | key[8] = secData[3]; 146 | key[9] = secData[0]; 147 | key[10] = secData[2]; 148 | key[11] = secData[1]; 149 | key[12] = secData[0]; 150 | key[13] = secData[3]; 151 | key[14] = secData[1]; 152 | key[15] = secData[2]; 153 | 154 | // decrypt 155 | var decipher = crypto.createDecipheriv('aes128', key, iv); 156 | 157 | decipher.setAutoPadding(false); 158 | authService2Data = decipher.update(authService2Data); 159 | 160 | this.writeAuthService2(authService2Data, function(error) { 161 | callback(error); 162 | }.bind(this)); 163 | }.bind(this)); 164 | }.bind(this)); 165 | }; 166 | 167 | 168 | Estimote.prototype.writeDeviceName = function(deviceName, callback) { 169 | this.writeStringCharacteristic(GENERIC_ACCESS_SERVICE_UUID, DEVICE_NAME_UUID, deviceName, callback); 170 | }; 171 | 172 | Estimote.prototype.readMajor = function(callback) { 173 | this.readUInt16LECharacteristic(ESTIMOTE_SERVICE_UUID, MAJOR_UUID, callback); 174 | }; 175 | 176 | Estimote.prototype.writeMajor = function(major, callback) { 177 | this.writeUInt16LECharacteristic(ESTIMOTE_SERVICE_UUID, MAJOR_UUID, major, callback); 178 | }; 179 | 180 | Estimote.prototype.readMinor = function(callback) { 181 | this.readUInt16LECharacteristic(ESTIMOTE_SERVICE_UUID, MINOR_UUID, callback); 182 | }; 183 | 184 | Estimote.prototype.writeMinor = function(minor, callback) { 185 | this.writeUInt16LECharacteristic(ESTIMOTE_SERVICE_UUID, MINOR_UUID, minor, callback); 186 | }; 187 | 188 | Estimote.prototype.readUuid1 = function(callback) { 189 | this.readDataCharacteristic(ESTIMOTE_SERVICE_UUID, UUID_1_UUID, function(error, data) { 190 | if (error) { 191 | return callback(error); 192 | } 193 | 194 | callback(null, data.toString('hex')); 195 | }); 196 | }; 197 | 198 | Estimote.prototype.writeUuid1 = function(uuid1, callback) { 199 | this.writeDataCharacteristic(ESTIMOTE_SERVICE_UUID, UUID_1_UUID, new Buffer(uuid1, 'hex'), callback); 200 | }; 201 | 202 | Estimote.prototype.readUuid2 = function(callback) { 203 | this.readDataCharacteristic(ESTIMOTE_SERVICE_UUID, UUID_2_UUID, function(error, data) { 204 | if (error) { 205 | return callback(error); 206 | } 207 | 208 | callback(null, data.toString('hex')); 209 | }); 210 | }; 211 | 212 | Estimote.prototype.writeUuid2 = function(uuid2, callback) { 213 | this.writeDataCharacteristic(ESTIMOTE_SERVICE_UUID, UUID_2_UUID, new Buffer(uuid2, 'hex'), callback); 214 | }; 215 | 216 | Estimote.prototype.readPowerLevel = function(callback) { 217 | this.readUInt8Characteristic(ESTIMOTE_SERVICE_UUID, POWER_LEVEL_UUID, function(error, rawLevel) { 218 | var POWER_LEVEL_MAPPER = { 219 | '-30': 1, 220 | '-20': 2, 221 | '-16': 3, 222 | '-12': 4, 223 | '-8': 5, 224 | '-4': 6, 225 | '0': 7, 226 | '4': 8 227 | }; 228 | 229 | if (error) { 230 | return callback(error); 231 | } 232 | 233 | var powerLevel = POWER_LEVEL_MAPPER['' + rawLevel]; 234 | 235 | if (powerLevel === undefined) { 236 | powerLevel = 'unknown'; 237 | } 238 | 239 | callback(error, powerLevel, rawLevel); 240 | }.bind(this)); 241 | }; 242 | 243 | Estimote.prototype.writePowerLevel = function(powerLevel, callback) { 244 | if (powerLevel < 1) { 245 | powerLevel = 1; 246 | } else if (powerLevel > 8) { 247 | powerLevel = 8; 248 | } 249 | 250 | var POWER_LEVEL_MAPPER = { 251 | 1: -30, 252 | 2: -20, 253 | 3: -16, 254 | 4: -12, 255 | 5: -8, 256 | 6: -4, 257 | 7: 0, 258 | 8: 4 259 | }; 260 | 261 | var rawLevel = POWER_LEVEL_MAPPER[powerLevel]; 262 | 263 | this.writeUInt8Characteristic(ESTIMOTE_SERVICE_UUID, POWER_LEVEL_UUID, rawLevel, callback); 264 | }; 265 | 266 | Estimote.prototype.readAdvertisementInterval = function(callback) { 267 | // 50 -> 0x5000 -> 80 268 | // 200 -> 0x4001 -> 320 269 | // 2000 -> 0x800c -> 3200 270 | 271 | this.readUInt16LECharacteristic(ESTIMOTE_SERVICE_UUID, ADVERTISEMENT_INTERVAL_UUID, function(error, rawAdvertisementInterval) { 272 | if (error) { 273 | return callback(error); 274 | } 275 | 276 | var advertisementInterval = (rawAdvertisementInterval / 8) * 5; 277 | 278 | callback(null, advertisementInterval); 279 | }.bind(this)); 280 | }; 281 | 282 | Estimote.prototype.writeAdvertisementInterval = function(advertisementInterval, callback) { 283 | var rawAdvertisementInterval = (advertisementInterval / 5) * 8; 284 | 285 | this.writeUInt16LECharacteristic(ESTIMOTE_SERVICE_UUID, ADVERTISEMENT_INTERVAL_UUID, rawAdvertisementInterval, callback); 286 | }; 287 | 288 | Estimote.prototype.readTemperature = function(callback) { 289 | this.writeUInt16LECharacteristic(ESTIMOTE_SERVICE_UUID, TEMPERATURE_UUID, 0xffff, function(error) { 290 | if (error) { 291 | return callback(error); 292 | } 293 | 294 | this.readUInt16LECharacteristic(ESTIMOTE_SERVICE_UUID, TEMPERATURE_UUID, function(error, temperature) { 295 | if (error) { 296 | return callback(error); 297 | } 298 | 299 | callback(null, temperature / 256.0); 300 | }); 301 | }.bind(this)); 302 | }; 303 | 304 | Estimote.prototype.subscribeMotion = function(callback) { 305 | this.notifyCharacteristic(ESTIMOTE_SERVICE_UUID, MOTION_UUID, true, this._onMotionDataBinded, callback); 306 | }; 307 | 308 | Estimote.prototype.unsubscribeMotion = function(callback) { 309 | this.notifyCharacteristic(ESTIMOTE_SERVICE_UUID, MOTION_UUID, false, this._onMotionDataBinded, callback); 310 | }; 311 | 312 | Estimote.prototype.onMotionData = function(data) { 313 | this.emit('motionStateChange', data.readUInt8(0) ? true : false); 314 | }; 315 | 316 | Estimote.prototype.readService2_9 = function(callback) { 317 | this.readDataCharacteristic(ESTIMOTE_SERVICE_UUID, SERVICE_2_09_UUID, callback); 318 | }; 319 | 320 | Estimote.prototype.readService2_10 = function(callback) { 321 | this.readDataCharacteristic(ESTIMOTE_SERVICE_UUID, SERVICE_2_10_UUID, callback); 322 | }; 323 | 324 | Estimote.prototype.readBatteryLevel = function(callback) { 325 | this.readUInt8Characteristic(ESTIMOTE_SERVICE_UUID, BATTERY_LEVEL_UUID, callback); 326 | }; 327 | 328 | var SERVICE_CONFIGURATION_MAPPER = ['default', 'eddystone-uid', 'eddystone-url']; 329 | 330 | Estimote.prototype.readServiceConfiguration = function(callback) { 331 | this.readDataCharacteristic(ESTIMOTE_SERVICE_UUID, SERVICE_CONFIGURATION_UUID, function(error, value) { 332 | if (error) { 333 | return callback(error); 334 | } 335 | 336 | callback(null, SERVICE_CONFIGURATION_MAPPER[value[3] & 0x03]); 337 | }.bind(this)); 338 | }; 339 | 340 | Estimote.prototype.writeServiceConfiguration = function(serviceConfig, callback) { 341 | this.readDataCharacteristic(ESTIMOTE_SERVICE_UUID, SERVICE_CONFIGURATION_UUID, function(error, value) { 342 | if (error) { 343 | return callback(error); 344 | } 345 | 346 | value[3] &= 0xfc; 347 | value[3] |= SERVICE_CONFIGURATION_MAPPER.indexOf(serviceConfig); 348 | 349 | this.writeDataCharacteristic(ESTIMOTE_SERVICE_UUID, SERVICE_CONFIGURATION_UUID, value, callback); 350 | }.bind(this)); 351 | }; 352 | 353 | Estimote.prototype.readEddystoneUidNamespace = function(callback) { 354 | this.readDataCharacteristic(ESTIMOTE_SERVICE_UUID, EDDYSTONE_UID_NAMESPACE_UUID, function(error, data) { 355 | if (error) { 356 | return callback(error); 357 | } 358 | 359 | callback(null, data.toString('hex')); 360 | }); 361 | }; 362 | 363 | Estimote.prototype.writeEddystoneUidNamespace = function(uidNamespace, callback) { 364 | this.writeDataCharacteristic(ESTIMOTE_SERVICE_UUID, EDDYSTONE_UID_NAMESPACE_UUID, new Buffer(uidNamespace, 'hex'), callback); 365 | }; 366 | 367 | Estimote.prototype.readEddystoneUidInstance = function(callback) { 368 | this.readDataCharacteristic(ESTIMOTE_SERVICE_UUID, EDDYSTONE_UID_INSTANCE_UUID, function(error, data) { 369 | if (error) { 370 | return callback(error); 371 | } 372 | 373 | callback(null, data.toString('hex')); 374 | }); 375 | }; 376 | 377 | Estimote.prototype.writeEddystoneUidInstance = function(uidInstance, callback) { 378 | this.writeDataCharacteristic(ESTIMOTE_SERVICE_UUID, EDDYSTONE_UID_INSTANCE_UUID, new Buffer(uidInstance, 'hex'), callback); 379 | }; 380 | 381 | Estimote.prototype.readEddystoneUrl = function(callback) { 382 | this.readDataCharacteristic(ESTIMOTE_SERVICE_UUID, EDDYSTONE_URL_UUID, callback); 383 | }; 384 | 385 | Estimote.prototype.writeEddystoneUrl = function(url, callback) { 386 | this.writeDataCharacteristic(ESTIMOTE_SERVICE_UUID, EDDYSTONE_URL_UUID, url, callback); 387 | }; 388 | 389 | Estimote.prototype.readAuthService1 = function(callback) { 390 | this.readUInt32LECharacteristic(AUTH_SERVICE_UUID, AUTH_SERVICE_1_UUID, callback); 391 | }; 392 | 393 | Estimote.prototype.writeAuthService1 = function(value, callback) { 394 | this.writeUInt32LECharacteristic(AUTH_SERVICE_UUID, AUTH_SERVICE_1_UUID, value, callback); 395 | }; 396 | 397 | Estimote.prototype.readAuthService2 = function(callback) { 398 | this.readDataCharacteristic(AUTH_SERVICE_UUID, AUTH_SERVICE_2_UUID, callback); 399 | }; 400 | 401 | Estimote.prototype.writeAuthService2 = function(data, callback) { 402 | this.writeDataCharacteristic(AUTH_SERVICE_UUID, AUTH_SERVICE_2_UUID, data, callback); 403 | }; 404 | 405 | Estimote.prototype.readFirmwareVersion = function(callback) { 406 | this.readStringCharacteristic(VERSION_SERVICE_UUID, FIRMWARE_VERSION_UUID, callback); 407 | }; 408 | 409 | Estimote.prototype.readHardwareVersion = function(callback) { 410 | this.readStringCharacteristic(VERSION_SERVICE_UUID, HARDWARE_VERSION_UUID, callback); 411 | }; 412 | 413 | module.exports = Estimote; 414 | -------------------------------------------------------------------------------- /estimote/estimote.txt: -------------------------------------------------------------------------------- 1 | Advertisement 2 | 3 | - name: estimote 4 | - service data 0x180a 5 | - manufacturer data 6 | 7 | 8 | 1800 9 | 2a00 read, write Device Name 10 | 2a01 read Appearance 11 | 2a04 read Peripheral Preferred Connection Parameters 12 | 13 | 1801 14 | 2a05 indicate 15 | 16 | b9401000f5f8466eaff925556b57fe6d (ota ???) 17 | b9401001f5f8466eaff925556b57fe6d write 18 | b9401002f5f8466eaff925556b57fe6d notify 19 | b9401003f5f8466eaff925556b57fe6d writeWithoutResponse 20 | 21 | b9403000f5f8466eaff925556b57fe6d (estimote) 22 | b9403001f5f8466eaff925556b57fe6d read, write Major 23 | b9403002f5f8466eaff925556b57fe6d read, write Minor 24 | b9403003f5f8466eaff925556b57fe6d read, write UUID (b9407f30f5f8466eaff925556b57fe6d) 25 | b9403004f5f8466eaff925556b57fe6d read, write UUID (b9407f30f5f8466eaff925556b57fe6d) 26 | b9403011f5f8466eaff925556b57fe6d read, write Power Level (f4 = -12 dBm) 27 | b9403012f5f8466eaff925556b57fe6d read, write AdvertisementInterval (4001 = 200 ms) 28 | b9403021f5f8466eaff925556b57fe6d read, write ??? (0000) 29 | b9403031f5f8466eaff925556b57fe6d read, indicate ??? (00) 30 | b9403032f5f8466eaff925556b57fe6d read, write ??? (00000000) 31 | b9403051f5f8466eaff925556b57fe6d read, write ??? (00000000) 32 | b9403041f5f8466eaff925556b57fe6d read, write Battery Level 33 | 34 | b9402000f5f8466eaff925556b57fe6d (auth) 35 | b9402001f5f8466eaff925556b57fe6d read, write pair sec/base 36 | b9402002f5f8466eaff925556b57fe6d read, write pair code 37 | 38 | b9404000f5f8466eaff925556b57fe6d (soft) 39 | b9404001f5f8466eaff925556b57fe6d read Firmware Version (A1.9) 40 | b9404002f5f8466eaff925556b57fe6d read Hardware Version (D3.2) 41 | 42 | 43 | http://assets.estimote.com/firmware/update.json 44 | -------------------------------------------------------------------------------- /estimote/pseudo.js: -------------------------------------------------------------------------------- 1 | var os = require('os'); 2 | var util = require('util'); 3 | 4 | var bleno = require('bleno'); 5 | 6 | var BlenoPrimaryService = bleno.PrimaryService; 7 | var BlenoCharacteristic = bleno.Characteristic; 8 | var BlenoDescriptor = bleno.Descriptor; 9 | 10 | if (os.platform() !== 'linux') { 11 | console.warn('this script only supports Linux!'); 12 | } 13 | 14 | console.log('pseudo - estimote'); 15 | 16 | bleno.on('stateChange', function(state) { 17 | console.log('on -> stateChange: ' + state); 18 | 19 | if (state === 'poweredOn') { 20 | bleno.startAdvertisingWithEIRData( 21 | new Buffer('0201061aff4c000215b9407f30f5f8466eaff925556b57fe6d00010002b6', 'hex'), 22 | new Buffer('0909657374696d6f74650e160a182eb8855fb5ddb601000200', 'hex') 23 | ); 24 | } else { 25 | bleno.stopAdvertising(); 26 | } 27 | }); 28 | 29 | bleno.on('advertisingStart', function(error) { 30 | console.log('on -> advertisingStart ' + error); 31 | 32 | if (!error) { 33 | bleno.setServices([ 34 | new BlenoPrimaryService({ 35 | uuid: 'b9401000f5f8466eaff925556b57fe6d', 36 | characteristics: [ 37 | new BlenoCharacteristic({ 38 | uuid: '39e1fd0184a811e2afba0002a5d5c51b', 39 | properties: ['write'] 40 | }), 41 | new BlenoCharacteristic({ 42 | uuid: 'b9401002f5f8466eaff925556b57fe6d', 43 | properties: ['notify'] 44 | }), 45 | new BlenoCharacteristic({ 46 | uuid: 'b9401003f5f8466eaff925556b57fe6d', 47 | properties: ['writeWithoutResponse'] 48 | }) 49 | ] 50 | }), 51 | new BlenoPrimaryService({ 52 | uuid: 'b9403000f5f8466eaff925556b57fe6d', 53 | characteristics: [ 54 | new BlenoCharacteristic({ 55 | uuid: 'b9403001f5f8466eaff925556b57fe6d', 56 | properties: ['read', 'write'], 57 | value: new Buffer('0200', 'hex') 58 | }), 59 | new BlenoCharacteristic({ 60 | uuid: 'b9403002f5f8466eaff925556b57fe6d', 61 | properties: ['read', 'write'], 62 | value: new Buffer('0100', 'hex') 63 | }), 64 | new BlenoCharacteristic({ 65 | uuid: 'b9403003f5f8466eaff925556b57fe6d', 66 | properties: ['read', 'write'], 67 | value: new Buffer('b9407f30f5f8466eaff925556b57fe6d', 'hex') 68 | }), 69 | new BlenoCharacteristic({ 70 | uuid: 'b9403004f5f8466eaff925556b57fe6d', 71 | properties: ['read', 'write'], 72 | value: new Buffer('b9407f30f5f8466eaff925556b57fe6d', 'hex') 73 | }), 74 | new BlenoCharacteristic({ 75 | uuid: 'b9403011f5f8466eaff925556b57fe6d', 76 | properties: ['read', 'write'], 77 | value: new Buffer('f4', 'hex') 78 | }), 79 | new BlenoCharacteristic({ 80 | uuid: 'b9403012f5f8466eaff925556b57fe6d', 81 | properties: ['read', 'write'], 82 | value: new Buffer('4001', 'hex') 83 | }), 84 | new BlenoCharacteristic({ 85 | uuid: 'b9403021f5f8466eaff925556b57fe6d', 86 | properties: ['read', 'write'], 87 | value: new Buffer('0000', 'hex') 88 | }), 89 | new BlenoCharacteristic({ 90 | uuid: 'b9403031f5f8466eaff925556b57fe6d', 91 | properties: ['read', 'indicate'], 92 | value: new Buffer('00', 'hex') 93 | }), 94 | new BlenoCharacteristic({ 95 | uuid: 'b9403032f5f8466eaff925556b57fe6d', 96 | properties: ['read', 'write'], 97 | value: new Buffer('00000000', 'hex') 98 | }), 99 | new BlenoCharacteristic({ 100 | uuid: 'b9403051f5f8466eaff925556b57fe6d', 101 | properties: ['read', 'write'], 102 | value: new Buffer('00000000', 'hex') 103 | }), 104 | new BlenoCharacteristic({ 105 | uuid: 'b9403041f5f8466eaff925556b57fe6d', 106 | properties: ['read', 'write'], 107 | value: new Buffer('64', 'hex') 108 | }) 109 | ] 110 | }), 111 | new BlenoPrimaryService({ 112 | uuid: 'b9402000f5f8466eaff925556b57fe6d', 113 | characteristics: [ 114 | new BlenoCharacteristic({ 115 | uuid: 'b9402001f5f8466eaff925556b57fe6d', 116 | properties: ['read', 'write'], 117 | value: new Buffer('00000000', 'hex') 118 | }), 119 | new BlenoCharacteristic({ 120 | uuid: 'b9402002f5f8466eaff925556b57fe6d', 121 | properties: ['read', 'write'], 122 | value: new Buffer('00000000000000000000000000000000', 'hex') 123 | }) 124 | ] 125 | }), 126 | new BlenoPrimaryService({ 127 | uuid: 'b9404000f5f8466eaff925556b57fe6d', 128 | characteristics: [ 129 | new BlenoCharacteristic({ 130 | uuid: 'b9404001f5f8466eaff925556b57fe6d', 131 | properties: ['read'], 132 | value: new Buffer('A1.9') 133 | }), 134 | new BlenoCharacteristic({ 135 | uuid: 'b9404002f5f8466eaff925556b57fe6d', 136 | properties: ['read'], 137 | value: new Buffer('D3.2') 138 | }) 139 | ] 140 | }), 141 | ]); 142 | } 143 | }); 144 | 145 | bleno.on('advertisingStop', function() { 146 | console.log('on -> advertisingStop'); 147 | }); 148 | 149 | bleno.on('servicesSet', function() { 150 | console.log('on -> servicesSet'); 151 | }); 152 | -------------------------------------------------------------------------------- /estimote/test.js: -------------------------------------------------------------------------------- 1 | var async = require('async'); 2 | 3 | var Estimote = require('./estimote.js'); 4 | 5 | Estimote.discover(function(estimote) { 6 | async.series([ 7 | function(callback) { 8 | estimote.on('disconnect', function() { 9 | console.log('disconnected!'); 10 | process.exit(0); 11 | }); 12 | 13 | estimote.on('motionStateChange', function(isMoving) { 14 | console.log('\tmotion state change: isMoving = ' + isMoving); 15 | }); 16 | 17 | console.log('found: ' + estimote.toString()); 18 | 19 | console.log('connectAndSetUp'); 20 | estimote.connectAndSetUp(callback); 21 | }, 22 | function(callback) { 23 | console.log('pair'); 24 | estimote.pair(callback); 25 | }, 26 | function(callback) { 27 | console.log('readMajor'); 28 | estimote.readMajor(function(error, major) { 29 | console.log('\tmajor = ' + major + ' (0x' + major.toString(16) + ')'); 30 | 31 | console.log('writeMajor'); 32 | estimote.writeMajor(major, callback); 33 | }); 34 | }, 35 | function(callback) { 36 | console.log('readMinor'); 37 | estimote.readMinor(function(error, minor) { 38 | console.log('\tminor = ' + minor + ' (0x' + minor.toString(16) + ')'); 39 | 40 | console.log('writeMinor'); 41 | estimote.writeMinor(minor, callback); 42 | }); 43 | }, 44 | function(callback) { 45 | console.log('readUuid1'); 46 | estimote.readUuid1(function(error, uuid1) { 47 | console.log('\tuuid 1 = ' + uuid1); 48 | 49 | console.log('writeUuid1'); 50 | estimote.writeUuid1(uuid1, callback); 51 | }); 52 | }, 53 | function(callback) { 54 | console.log('readUuid2'); 55 | estimote.readUuid2(function(error, uuid2) { 56 | console.log('\tuuid 2 = ' + uuid2); 57 | 58 | console.log('writeUuid2'); 59 | estimote.writeUuid2(uuid2, callback); 60 | }); 61 | }, 62 | function(callback) { 63 | console.log('readPowerLevel'); 64 | estimote.readPowerLevel(function(error, powerLevel, dBm) { 65 | console.log('\tpower level ' + powerLevel + ', ' + dBm + ' dBm'); 66 | 67 | console.log('writePowerLevel'); 68 | estimote.writePowerLevel(powerLevel, callback); 69 | }); 70 | }, 71 | function(callback) { 72 | console.log('readAdvertisementInterval'); 73 | estimote.readAdvertisementInterval(function(error, advertisementInterval) { 74 | console.log('\tadvertisement interval = ' + advertisementInterval + ' ms'); 75 | 76 | console.log('writeAdvertisementInterval'); 77 | estimote.writeAdvertisementInterval(advertisementInterval, callback); 78 | }); 79 | }, 80 | function(callback) { 81 | console.log('readTemperature'); 82 | estimote.readTemperature(function(error, temperature) { 83 | console.log('\ttemperature = ' + temperature.toFixed(1)); 84 | 85 | callback(); 86 | }); 87 | }, 88 | function(callback) { 89 | console.log('subscribeMotion'); 90 | estimote.subscribeMotion(callback); 91 | }, 92 | function(callback) { 93 | setTimeout(callback, 5000); 94 | }, 95 | function(callback) { 96 | console.log('unsubscribeMotion'); 97 | estimote.unsubscribeMotion(callback); 98 | }, 99 | function(callback) { 100 | console.log('readService2_9'); 101 | estimote.readService2_9(function(error, data) { 102 | console.log('\tservice 2 9 = ' + data.toString('hex')); 103 | 104 | callback(); 105 | }); 106 | }, 107 | function(callback) { 108 | console.log('readService2_10'); 109 | estimote.readService2_10(function(error, data) { 110 | console.log('\tservice 2 10 = ' + data.toString('hex')); 111 | 112 | callback(); 113 | }); 114 | }, 115 | function(callback) { 116 | console.log('readBatteryLevel'); 117 | estimote.readBatteryLevel(function(error, batteryLevel) { 118 | console.log('\tbattery level = ' + batteryLevel); 119 | 120 | callback(); 121 | }); 122 | }, 123 | function(callback) { 124 | console.log('readServiceConfiguration'); 125 | estimote.readServiceConfiguration(function(error, serviceConfiguration) { 126 | console.log('\tservice configuration = ' + serviceConfiguration); 127 | 128 | console.log('writeServiceConfiguration'); 129 | estimote.writeServiceConfiguration(serviceConfiguration, callback); 130 | }); 131 | }, 132 | function(callback) { 133 | console.log('readEddystoneUidNamespace'); 134 | estimote.readEddystoneUidNamespace(function(error, uidNamespace) { 135 | console.log('\tUID namespace = ' + uidNamespace); 136 | 137 | console.log('writeEddystoneUidNamespace'); 138 | estimote.writeEddystoneUidNamespace(uidNamespace, callback); 139 | }); 140 | }, 141 | function(callback) { 142 | console.log('readEddystoneUidInstance'); 143 | estimote.readEddystoneUidInstance(function(error, uidInstance) { 144 | console.log('\tUID instance = ' + uidInstance); 145 | 146 | console.log('writeEddystoneUidInstance'); 147 | estimote.writeEddystoneUidInstance(uidInstance, callback); 148 | }); 149 | }, 150 | function(callback) { 151 | console.log('readEddystoneUrl'); 152 | estimote.readEddystoneUrl(function(error, url) { 153 | console.log('\tURL = ' + url.toString('hex')); 154 | 155 | console.log('writeEddystoneUrl'); 156 | estimote.writeEddystoneUrl(url, callback); 157 | }); 158 | }, 159 | function(callback) { 160 | console.log('readFirmwareVersion'); 161 | estimote.readFirmwareVersion(function(error, firmwareVersion) { 162 | console.log('\tfirmware version = ' + firmwareVersion); 163 | 164 | callback(); 165 | }); 166 | }, 167 | function(callback) { 168 | console.log('readHardwareVersion'); 169 | estimote.readHardwareVersion(function(error, hardwareVersion) { 170 | console.log('\thardware version = ' + hardwareVersion); 171 | 172 | callback(); 173 | }); 174 | }, 175 | function(callback) { 176 | console.log('disconnect'); 177 | estimote.disconnect(callback); 178 | } 179 | ]); 180 | }); 181 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var Bleacon = require('./lib/bleacon'); 2 | 3 | var bleacon = new Bleacon(); 4 | 5 | bleacon.BleuStation = require('./bleu-station/bleu-station'); 6 | bleacon.Estimote = require('./estimote/estimote'); 7 | bleacon.EstimoteSticker = require('./estimote-sticker/estimote-sticker'); 8 | 9 | module.exports = bleacon; 10 | -------------------------------------------------------------------------------- /lib/bleacon.js: -------------------------------------------------------------------------------- 1 | var events = require('events'); 2 | var util = require('util'); 3 | 4 | var debug = require('debug')('bleacon'); 5 | 6 | var noble = require('noble'); 7 | var bleno = require('bleno'); 8 | 9 | var EXPECTED_MANUFACTURER_DATA_LENGTH = 25; 10 | var APPLE_COMPANY_IDENTIFIER = 0x004c; // https://www.bluetooth.org/en-us/specification/assigned-numbers/company-identifiers 11 | var IBEACON_TYPE = 0x02; 12 | var EXPECTED_IBEACON_DATA_LENGTH = 0x15; 13 | 14 | var Bleacon = function() { 15 | this._uuid = null; 16 | this._major = null; 17 | this._minor = null; 18 | 19 | this._discovered = {}; 20 | 21 | noble.on('discover', this.onDiscover.bind(this)); 22 | }; 23 | 24 | util.inherits(Bleacon, events.EventEmitter); 25 | 26 | Bleacon.prototype.startAdvertising = function(uuid, major, minor, measuredPower) { 27 | debug('startAdvertising: uuid = %s, major = %s, minor = %s, measuredPower = %s', uuid, major, minor, measuredPower); 28 | 29 | if (bleno.state === 'poweredOn') { 30 | bleno.startAdvertisingIBeacon(uuid, major, minor, measuredPower); 31 | } else { 32 | bleno.on('stateChange', function() { 33 | bleno.startAdvertisingIBeacon(uuid, major, minor, measuredPower); 34 | }); 35 | } 36 | }; 37 | 38 | Bleacon.prototype.stopAdvertising = function() { 39 | debug('stopAdvertising'); 40 | bleno.stopAdvertising(); 41 | }; 42 | 43 | Bleacon.prototype.startScanning = function(uuid, major, minor, noDuplicates) { 44 | debug('startScanning: uuid = %s, major = %s, minor = %s', uuid, major, minor); 45 | 46 | if(uuid && !Array.isArray(uuid)) { 47 | uuid = new Array(uuid); 48 | } 49 | 50 | this._uuid = uuid; 51 | this._major = major; 52 | this._minor = minor; 53 | 54 | var duplicates = !noDuplicates; 55 | 56 | if (noble.state === 'poweredOn') { 57 | noble.startScanning([], duplicates); 58 | } else { 59 | noble.on('stateChange', function() { 60 | noble.startScanning([], duplicates); 61 | }); 62 | } 63 | }; 64 | 65 | Bleacon.prototype.stopScanning = function() { 66 | debug('stopScanning'); 67 | noble.stopScanning(); 68 | }; 69 | 70 | Bleacon.prototype.onDiscover = function(peripheral) { 71 | debug('onDiscover: %s', peripheral); 72 | 73 | var manufacturerData = peripheral.advertisement.manufacturerData; 74 | var rssi = peripheral.rssi; 75 | 76 | debug('onDiscover: manufacturerData = %s, rssi = %d', manufacturerData ? manufacturerData.toString('hex') : null, rssi); 77 | 78 | if (manufacturerData && 79 | EXPECTED_MANUFACTURER_DATA_LENGTH <= manufacturerData.length && 80 | APPLE_COMPANY_IDENTIFIER === manufacturerData.readUInt16LE(0) && 81 | IBEACON_TYPE === manufacturerData.readUInt8(2) && 82 | EXPECTED_IBEACON_DATA_LENGTH === manufacturerData.readUInt8(3)) { 83 | 84 | var uuid = manufacturerData.slice(4, 20).toString('hex'); 85 | var major = manufacturerData.readUInt16BE(20); 86 | var minor = manufacturerData.readUInt16BE(22); 87 | var measuredPower = manufacturerData.readInt8(24); 88 | 89 | debug('onDiscover: uuid = %s, major = %d, minor = %d, measuredPower = %d', uuid, major, minor, measuredPower); 90 | 91 | if ((!this._uuid || this._uuid.indexOf(uuid) != -1 ) && 92 | (!this._major || this._major === major) && 93 | (!this._minor || this._minor === minor)) { 94 | 95 | var accuracy = Math.pow(12.0, 1.5 * ((rssi / measuredPower) - 1)); 96 | var proximity = null; 97 | 98 | if (accuracy < 0) { 99 | proximity = 'unknown'; 100 | } else if (accuracy < 0.5) { 101 | proximity = 'immediate'; 102 | } else if (accuracy < 4.0) { 103 | proximity = 'near'; 104 | } else { 105 | proximity = 'far'; 106 | } 107 | 108 | var bleacon = this._discovered[peripheral.uuid]; 109 | 110 | if (!bleacon) { 111 | bleacon = {}; 112 | } 113 | 114 | bleacon.uuid = uuid; 115 | bleacon.major = major; 116 | bleacon.minor = minor; 117 | bleacon.measuredPower = measuredPower; 118 | bleacon.rssi = rssi; 119 | bleacon.accuracy = accuracy; 120 | bleacon.proximity = proximity; 121 | 122 | this._discovered[peripheral.uuid] = bleacon; 123 | 124 | debug('onDiscover: bleacon = %s', JSON.stringify(bleacon)); 125 | 126 | this.emit('discover', bleacon); 127 | } 128 | } 129 | }; 130 | 131 | module.exports = Bleacon; 132 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bleacon", 3 | "version": "0.5.1", 4 | "description": "A Node.js library for creating, discovering, and configuring iBeacons", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "jshint *.js lib/*.js bleu-station/*.js estimote/*.js estimote-sticker/*.js radbeacon/*.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/sandeepmistry/node-bleacon.git" 12 | }, 13 | "keywords": [ 14 | "ibeacon" 15 | ], 16 | "author": "Sandeep Mistry", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/sandeepmistry/node-bleacon/issues" 20 | }, 21 | "dependencies": { 22 | "bignum": "^0.12.5", 23 | "bleno": "^0.4.1", 24 | "debug": "^2.2.0", 25 | "noble": "^1.7.0", 26 | "noble-device": "^1.1.0" 27 | }, 28 | "devDependencies": { 29 | "jshint": "~2.1.11", 30 | "async": "~0.2.9" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /radbeacon/pseudo-radbeacon-tag.js: -------------------------------------------------------------------------------- 1 | var os = require('os'); 2 | var util = require('util'); 3 | 4 | var bleno = require('bleno'); 5 | 6 | var BlenoPrimaryService = bleno.PrimaryService; 7 | var BlenoCharacteristic = bleno.Characteristic; 8 | var BlenoDescriptor = bleno.Descriptor; 9 | 10 | console.log('pseudo - radbeacon tag'); 11 | 12 | bleno.on('stateChange', function(state) { 13 | console.log('on -> stateChange: ' + state); 14 | 15 | if (state === 'poweredOn') { 16 | bleno.startAdvertising('RadBeacon'); 17 | } else { 18 | bleno.stopAdvertising(); 19 | } 20 | }); 21 | 22 | var lastCommand = null; 23 | 24 | bleno.on('advertisingStart', function(error) { 25 | console.log('on -> advertisingStart ' + error); 26 | 27 | if (!error) { 28 | bleno.setServices([ 29 | new BlenoPrimaryService({ 30 | uuid: '248e4f81e46c4762bf3f84069c5c3f09', 31 | characteristics: [ 32 | new BlenoCharacteristic({ 33 | uuid: '4f82', 34 | properties: ['write'], 35 | onWriteRequest: function(data, offset, withoutResponse, callback) { 36 | console.log('4f82 onWriteRequest ' + data.toString('hex')); 37 | 38 | lastCommand = 'pin'; 39 | 40 | callback(BlenoCharacteristic.RESULT_SUCCESS); 41 | } 42 | }), 43 | new BlenoCharacteristic({ 44 | uuid: '4f83', 45 | properties: ['write'], 46 | onWriteRequest: function(data, offset, withoutResponse, callback) { 47 | console.log('4f83 onWriteRequest ' + data.toString('hex')); 48 | 49 | lastCommand = data[0]; 50 | 51 | callback(BlenoCharacteristic.RESULT_SUCCESS); 52 | } 53 | }), 54 | new BlenoCharacteristic({ 55 | uuid: '4f84', 56 | properties: ['write'], 57 | onWriteRequest: function(data, offset, withoutResponse, callback) { 58 | console.log('4f84 onWriteRequest ' + data.toString('hex')); 59 | 60 | callback(BlenoCharacteristic.RESULT_SUCCESS); 61 | } 62 | }), 63 | new BlenoCharacteristic({ 64 | uuid: '4f85', 65 | properties: ['read', 'notify'], 66 | onReadRequest: function(offset, callback) { 67 | console.log('4f85 onReadRequest'); 68 | 69 | var data; 70 | 71 | if (lastCommand === 'pin') { 72 | data = new Buffer('01', 'hex'); // 01 - correct pin, 00 - incorrect pin 73 | } else if (lastCommand === 0x87) { 74 | data = new Buffer('RadBeacon Tag'); 75 | } else if (lastCommand === 0x90) { 76 | data = new Buffer('e2c56db5dffb48d2b060d0f5a71096e0', 'hex'); 77 | } else if (lastCommand === 0x91) { 78 | data = new Buffer('0001', 'hex'); 79 | } else if (lastCommand === 0x92) { 80 | data = new Buffer('0002', 'hex'); 81 | } else if (lastCommand === 0x93) { 82 | data = new Buffer('c5', 'hex'); 83 | } else if (lastCommand === 0x84) { 84 | data = new Buffer('04', 'hex'); 85 | } else if (lastCommand === 0x82) { 86 | data = new Buffer('0064', 'hex'); 87 | } else if (lastCommand === 0xa0) { 88 | data = new Buffer('32', 'hex'); 89 | } 90 | 91 | callback(BlenoCharacteristic.RESULT_SUCCESS, data); 92 | }, 93 | onSubscribe: function(maxValueSize, updateValueCallback) { 94 | console.log('4f85 onSubscribe'); 95 | }, 96 | onUnsubscribe: function() { 97 | console.log('4f85 onUnsubscribe'); 98 | } 99 | }) 100 | ] 101 | }) 102 | ]); 103 | } 104 | }); 105 | 106 | bleno.on('advertisingStop', function() { 107 | console.log('on -> advertisingStop'); 108 | }); 109 | 110 | bleno.on('servicesSet', function() { 111 | console.log('on -> servicesSet'); 112 | }); 113 | -------------------------------------------------------------------------------- /radbeacon/pseudo-radbeacon-usb.js: -------------------------------------------------------------------------------- 1 | var os = require('os'); 2 | var util = require('util'); 3 | 4 | var bleno = require('bleno'); 5 | 6 | var BlenoPrimaryService = bleno.PrimaryService; 7 | var BlenoCharacteristic = bleno.Characteristic; 8 | var BlenoDescriptor = bleno.Descriptor; 9 | 10 | console.log('pseudo - radbeacon usb'); 11 | 12 | bleno.on('stateChange', function(state) { 13 | console.log('on -> stateChange: ' + state); 14 | 15 | if (state === 'poweredOn') { 16 | bleno.startAdvertising('RadBeacon USB'); 17 | } else { 18 | bleno.stopAdvertising(); 19 | } 20 | }); 21 | 22 | var lastCommand = null; 23 | 24 | bleno.on('advertisingStart', function(error) { 25 | console.log('on -> advertisingStart ' + error); 26 | 27 | if (!error) { 28 | bleno.setServices([ 29 | new BlenoPrimaryService({ 30 | uuid: '0a89139fba2b4003a72d18e332be098c', 31 | characteristics: [ 32 | new BlenoCharacteristic({ 33 | uuid: 'aaa0', 34 | properties: ['read', 'write'], 35 | onReadRequest: function(offset, callback) { 36 | console.log('aaa0 onReadRequest'); 37 | 38 | callback(BlenoCharacteristic.RESULT_SUCCESS, new Buffer('526164426561636f6e20555342000000000000000000000000000000000000000000000000', 'hex')); 39 | }, 40 | onWriteRequest: function(data, offset, withoutResponse, callback) { 41 | console.log('aaa0 onWriteRequest ' + data.toString('hex')); 42 | 43 | callback(BlenoCharacteristic.RESULT_SUCCESS); 44 | } 45 | }), 46 | new BlenoCharacteristic({ 47 | uuid: 'aaa1', 48 | properties: ['read', 'write'], 49 | onReadRequest: function(offset, callback) { 50 | console.log('aaa1 onReadRequest'); 51 | 52 | callback(BlenoCharacteristic.RESULT_SUCCESS, new Buffer('303235354433', 'hex')); 53 | } 54 | }), 55 | new BlenoCharacteristic({ 56 | uuid: 'aaa2', 57 | properties: ['read', 'write'], 58 | onReadRequest: function(offset, callback) { 59 | console.log('aaa2 onReadRequest'); 60 | 61 | callback(BlenoCharacteristic.RESULT_SUCCESS, new Buffer('526164426561636f6e20555342000000000000000000000000000000000000000000000000', 'hex')); 62 | }, 63 | onWriteRequest: function(data, offset, withoutResponse, callback) { 64 | console.log('aaa2 onWriteRequest ' + data.toString('hex')); 65 | 66 | callback(BlenoCharacteristic.RESULT_SUCCESS); 67 | } 68 | }), 69 | new BlenoCharacteristic({ 70 | uuid: 'aaa3', 71 | properties: ['read', 'write'], 72 | onReadRequest: function(offset, callback) { 73 | console.log('aaa3 onReadRequest'); 74 | 75 | callback(BlenoCharacteristic.RESULT_SUCCESS, new Buffer('2f234454cf6d4a0fadf2f4911ba9ffa6', 'hex')); 76 | }, 77 | onWriteRequest: function(data, offset, withoutResponse, callback) { 78 | console.log('aaa3 onWriteRequest ' + data.toString('hex')); 79 | 80 | callback(BlenoCharacteristic.RESULT_SUCCESS); 81 | } 82 | }), 83 | new BlenoCharacteristic({ 84 | uuid: 'aaa4', 85 | properties: ['read', 'write'], 86 | onReadRequest: function(offset, callback) { 87 | console.log('aaa4 onReadRequest'); 88 | 89 | callback(BlenoCharacteristic.RESULT_SUCCESS, new Buffer('0001', 'hex')); 90 | }, 91 | onWriteRequest: function(data, offset, withoutResponse, callback) { 92 | console.log('aaa4 onWriteRequest ' + data.toString('hex')); 93 | 94 | callback(BlenoCharacteristic.RESULT_SUCCESS); 95 | } 96 | }), 97 | new BlenoCharacteristic({ 98 | uuid: 'aaa5', 99 | properties: ['read', 'write'], 100 | onReadRequest: function(offset, callback) { 101 | console.log('aaa5 onReadRequest'); 102 | 103 | callback(BlenoCharacteristic.RESULT_SUCCESS, new Buffer('0001', 'hex')); 104 | }, 105 | onWriteRequest: function(data, offset, withoutResponse, callback) { 106 | console.log('aaa5 onWriteRequest ' + data.toString('hex')); 107 | 108 | callback(BlenoCharacteristic.RESULT_SUCCESS); 109 | } 110 | }), 111 | new BlenoCharacteristic({ 112 | uuid: 'aaa6', 113 | properties: ['read', 'write'], 114 | onReadRequest: function(offset, callback) { 115 | console.log('aaa6 onReadRequest'); 116 | 117 | callback(BlenoCharacteristic.RESULT_SUCCESS, new Buffer('be', 'hex')); 118 | }, 119 | onWriteRequest: function(data, offset, withoutResponse, callback) { 120 | console.log('aaa6 onWriteRequest ' + data.toString('hex')); 121 | 122 | callback(BlenoCharacteristic.RESULT_SUCCESS); 123 | } 124 | }), 125 | new BlenoCharacteristic({ 126 | uuid: 'aaa7', 127 | properties: ['read', 'write'], 128 | onReadRequest: function(offset, callback) { 129 | console.log('aaa7 onReadRequest'); 130 | 131 | callback(BlenoCharacteristic.RESULT_SUCCESS, new Buffer('0f', 'hex')); 132 | }, 133 | onWriteRequest: function(data, offset, withoutResponse, callback) { 134 | console.log('aaa7 onWriteRequest ' + data.toString('hex')); 135 | 136 | callback(BlenoCharacteristic.RESULT_SUCCESS); 137 | } 138 | }), 139 | new BlenoCharacteristic({ 140 | uuid: 'aaa8', 141 | properties: ['read', 'write'], 142 | onReadRequest: function(offset, callback) { 143 | console.log('aaa8 onReadRequest'); 144 | 145 | callback(BlenoCharacteristic.RESULT_SUCCESS, new Buffer('a000', 'hex')); 146 | }, 147 | onWriteRequest: function(data, offset, withoutResponse, callback) { 148 | console.log('aaa8 onWriteRequest ' + data.toString('hex')); 149 | 150 | callback(BlenoCharacteristic.RESULT_SUCCESS); 151 | } 152 | }), 153 | new BlenoCharacteristic({ 154 | uuid: 'aaa9', 155 | properties: ['read', 'write'], 156 | onReadRequest: function(offset, callback) { 157 | console.log('aaa9 onReadRequest'); 158 | 159 | callback(BlenoCharacteristic.RESULT_SUCCESS, new Buffer('00000000', 'hex')); 160 | }, 161 | onWriteRequest: function(data, offset, withoutResponse, callback) { 162 | console.log('aaa9 onWriteRequest ' + data.toString('hex')); 163 | 164 | callback(BlenoCharacteristic.RESULT_SUCCESS); 165 | } 166 | }), 167 | new BlenoCharacteristic({ 168 | uuid: 'aaaa', 169 | properties: ['write'], 170 | onWriteRequest: function(data, offset, withoutResponse, callback) { 171 | console.log('aaaa onWriteRequest ' + data.toString('hex')); 172 | 173 | callback(BlenoCharacteristic.RESULT_SUCCESS); 174 | } 175 | }), 176 | new BlenoCharacteristic({ 177 | uuid: 'aaab', 178 | properties: ['write'], 179 | onWriteRequest: function(data, offset, withoutResponse, callback) { 180 | console.log('aaaa onWriteRequest ' + data.toString('hex')); 181 | 182 | callback(BlenoCharacteristic.RESULT_SUCCESS); 183 | } 184 | }), 185 | new BlenoCharacteristic({ 186 | uuid: 'aaac', 187 | properties: ['write'], 188 | onWriteRequest: function(data, offset, withoutResponse, callback) { 189 | console.log('aaaa onWriteRequest ' + data.toString('hex')); 190 | 191 | callback(BlenoCharacteristic.RESULT_SUCCESS); 192 | } 193 | }) 194 | ] 195 | }) 196 | ]); 197 | } 198 | }); 199 | 200 | bleno.on('advertisingStop', function() { 201 | console.log('on -> advertisingStop'); 202 | }); 203 | 204 | bleno.on('servicesSet', function() { 205 | console.log('on -> servicesSet'); 206 | }); 207 | -------------------------------------------------------------------------------- /radbeacon/radbeacon-tag.js: -------------------------------------------------------------------------------- 1 | var events = require('events'); 2 | var util = require('util'); 3 | 4 | var debug = require('debug')('radbeacon-tag'); 5 | 6 | var noble = require('noble'); 7 | 8 | var SERVICE_UUID = '248e4f81e46c4762bf3f84069c5c3f09'; 9 | 10 | var PIN_CODE_UUID = '4f82'; 11 | var COMMAND_UUID = '4f83'; 12 | var VALUE_UUID = '4f84'; 13 | var RESPONSE_UUID = '4f85'; 14 | 15 | var RadBeaconTag = function(peripheral) { 16 | this._peripheral = peripheral; 17 | this._services = {}; 18 | this._characteristics = {}; 19 | 20 | this.uuid = peripheral.uuid; 21 | this.name = peripheral.advertisement.localName; 22 | 23 | this._peripheral.on('disconnect', this.onDisconnect.bind(this)); 24 | }; 25 | 26 | util.inherits(RadBeaconTag, events.EventEmitter); 27 | 28 | RadBeaconTag.is = function(peripheral) { 29 | var localName = peripheral.advertisement.localName; 30 | 31 | return (localName === 'RadBeacon' && peripheral.advertisement.manufacturerData); 32 | }; 33 | 34 | RadBeaconTag.discover = function(callback) { 35 | var startScanningOnPowerOn = function() { 36 | if (noble.state === 'poweredOn') { 37 | var onDiscover = function(peripheral) { 38 | if (!RadBeaconTag.is(peripheral)) { 39 | return; 40 | } 41 | 42 | noble.removeListener('discover', onDiscover); 43 | 44 | noble.stopScanning(); 45 | 46 | var radbeaconTag = new RadBeaconTag(peripheral); 47 | 48 | callback(radbeaconTag); 49 | }; 50 | 51 | noble.on('discover', onDiscover); 52 | 53 | noble.startScanning([]); 54 | } else { 55 | noble.once('stateChange', startScanningOnPowerOn); 56 | } 57 | }; 58 | 59 | startScanningOnPowerOn(); 60 | }; 61 | 62 | RadBeaconTag.prototype.toString = function() { 63 | return JSON.stringify({ 64 | uuid: this.uuid, 65 | name: this.name 66 | }); 67 | }; 68 | 69 | RadBeaconTag.prototype.onDisconnect = function() { 70 | this.emit('disconnect'); 71 | }; 72 | 73 | RadBeaconTag.prototype.connect = function(callback) { 74 | this._peripheral.connect(callback); 75 | }; 76 | 77 | RadBeaconTag.prototype.disconnect = function(callback) { 78 | this._peripheral.disconnect(callback); 79 | }; 80 | 81 | RadBeaconTag.prototype.discoverServicesAndCharacteristics = function(callback) { 82 | this._peripheral.discoverAllServicesAndCharacteristics(function(error, services, characteristics) { 83 | if (error === null) { 84 | for (var i in services) { 85 | var service = services[i]; 86 | this._services[service.uuid] = service; 87 | } 88 | 89 | for (var j in characteristics) { 90 | var characteristic = characteristics[j]; 91 | 92 | this._characteristics[characteristic.uuid] = characteristic; 93 | } 94 | } 95 | 96 | callback(error); 97 | }.bind(this)); 98 | }; 99 | 100 | RadBeaconTag.prototype.readValue = function(command, callback) { 101 | this._characteristics[COMMAND_UUID].write(new Buffer([command]), false, function() { 102 | this._characteristics[RESPONSE_UUID].read(function(error, data) { 103 | callback(data); 104 | }.bind(this)); 105 | }.bind(this)); 106 | }; 107 | 108 | RadBeaconTag.prototype.writeValue = function(command, value, callback) { 109 | this.readValue(command, function() { 110 | this._characteristics[VALUE_UUID].write(value, false, function() { 111 | this._characteristics[RESPONSE_UUID].read(function(error, data) { 112 | callback(); 113 | }.bind(this)); 114 | }.bind(this)); 115 | }.bind(this)); 116 | }; 117 | 118 | RadBeaconTag.prototype.readName = function(callback) { 119 | this.readValue(0x87, function(data) { 120 | callback(data.toString()); 121 | }.bind(this)); 122 | }; 123 | 124 | RadBeaconTag.prototype.readUuid = function(callback) { 125 | this.readValue(0x90, function(data) { 126 | callback(data.toString('hex')); 127 | }.bind(this)); 128 | }; 129 | 130 | RadBeaconTag.prototype.readMajor = function(callback) { 131 | this.readValue(0x91, function(data) { 132 | callback(data.readUInt16BE(0)); 133 | }.bind(this)); 134 | }; 135 | 136 | RadBeaconTag.prototype.readMinor = function(callback) { 137 | this.readValue(0x92, function(data) { 138 | callback(data.readUInt16BE(0)); 139 | }.bind(this)); 140 | }; 141 | 142 | RadBeaconTag.prototype.readMeasuredTxPower = function(callback) { 143 | this.readValue(0x93, function(data) { 144 | callback(data.readInt8(0)); 145 | }.bind(this)); 146 | }; 147 | 148 | RadBeaconTag.prototype.readTxPower = function(callback) { 149 | this.readValue(0x84, function(data) { 150 | callback(data.readInt8(0)); 151 | }.bind(this)); 152 | }; 153 | 154 | RadBeaconTag.prototype.readAdvertisementInterval = function(callback) { 155 | this.readValue(0x82, function(data) { 156 | callback(data.readUInt16BE(0)); 157 | }.bind(this)); 158 | }; 159 | 160 | RadBeaconTag.prototype.readBatteryLevel = function(callback) { 161 | this.readValue(0xa0, function(data) { 162 | callback(data.readUInt8(0)); 163 | }.bind(this)); 164 | }; 165 | 166 | RadBeaconTag.prototype.login = function(pin, callback) { 167 | var data = new Buffer(4); 168 | 169 | pin += ''; 170 | 171 | for (var i = 0; i < data.length; i++) { 172 | data[i] = parseInt(pin.charAt(i), 10); 173 | } 174 | 175 | this._characteristics[PIN_CODE_UUID].write(data, false, function() { 176 | this._characteristics[RESPONSE_UUID].read(function(error, data) { 177 | callback(data.readInt8(0) === 1); 178 | }.bind(this)); 179 | }.bind(this)); 180 | }; 181 | 182 | RadBeaconTag.prototype.writeName = function(name, callback) { 183 | this.writeValue(0x07, new Buffer(name), callback); 184 | }; 185 | 186 | RadBeaconTag.prototype.writeUuid = function(uuid, callback) { 187 | this.writeValue(0x10, new Buffer(uuid, 'hex'), callback); 188 | }; 189 | 190 | RadBeaconTag.prototype.writeMajor = function(major, callback) { 191 | var data = new Buffer(2); 192 | 193 | data.writeUInt16BE(major, 0); 194 | 195 | this.writeValue(0x11, data, callback); 196 | }; 197 | 198 | RadBeaconTag.prototype.writeMinor = function(minor, callback) { 199 | var data = new Buffer(2); 200 | 201 | data.writeUInt16BE(minor, 0); 202 | 203 | this.writeValue(0x12, data, callback); 204 | }; 205 | 206 | RadBeaconTag.prototype.writeMeasuredTxPower = function(measureTxPower, callback) { 207 | var data = new Buffer(1); 208 | 209 | data.writeInt8(measureTxPower, 0); 210 | 211 | this.writeValue(0x13, data, callback); 212 | }; 213 | 214 | RadBeaconTag.prototype.writeAdvertisementInterval = function(advertisementInterval, callback) { 215 | var data = new Buffer(2); 216 | 217 | data.writeUInt16BE(advertisementInterval, 0); 218 | 219 | this.writeValue(0x02, data, callback); 220 | }; 221 | 222 | RadBeaconTag.prototype.writeTxPower = function(txPower, callback) { 223 | var data = new Buffer(1); 224 | 225 | data.writeInt8(txPower, 0); 226 | 227 | this.writeValue(0x04, data, callback); 228 | }; 229 | 230 | RadBeaconTag.prototype.writePin = function(currentpin, newpin, callback) { 231 | var data = new Buffer(4); 232 | 233 | newpin += ''; 234 | 235 | for (var i = 0; i < data.length; i++) { 236 | data[i] = parseInt(newpin.charAt(i), 10); 237 | } 238 | 239 | this.login(currentpin, function() { 240 | this.writeValue(0x01, data, callback); 241 | }.bind(this)); 242 | }; 243 | 244 | module.exports = RadBeaconTag; 245 | -------------------------------------------------------------------------------- /radbeacon/radbeacon-tag.txt: -------------------------------------------------------------------------------- 1 | 2 | peripheral with UUID 3fd82b6487614561886122481bba4409 found 3 | Local Name = RadBeacon 4 | Manufacturer Data = 4c0002152f234454cf6d4a0fadf2f4911ba9ffa600010120c5 5 | Service Data = 6 | Service UUIDs = 7 | 8 | services and characteristics: 9 | 1800 (Generic Access) 10 | 2a00 (Device Name) 11 | properties read, write 12 | value 526164426561636f6e | 'RadBeacon' 13 | 2a01 (Appearance) 14 | properties read 15 | value 4103 | 'A' 16 | 2a04 (Peripheral Preferred Connection Parameters) 17 | properties read 18 | value 9001200300009001 | ' ' 19 | 1801 (Generic Attribute) 20 | 2a05 (Service Changed) 21 | properties indicate 22 | 248e4f81e46c4762bf3f84069c5c3f09 23 | 4f82 24 | properties write 25 | 4f83 26 | properties write 27 | 4f84 28 | properties write 29 | 4f85 30 | properties read, notify 31 | value 0000000000000000000000000000000000000000 | '' 32 | 33 | 34 | 4f83 onWriteRequest 87 (Name) 35 | 4f85 onReadRequest 36 | 37 | 4f83 onWriteRequest 90 (UUID) 38 | 4f85 onReadRequest 39 | 40 | 4f83 onWriteRequest 91 (Major) 41 | 4f85 onReadRequest 42 | 43 | 4f83 onWriteRequest 92 (Minor) 44 | 4f85 onReadRequest 45 | 46 | 4f83 onWriteRequest 93 (measured) 47 | 4f85 onReadRequest 48 | 49 | 4f83 onWriteRequest 84 (power level) 50 | 4f85 onReadRequest 51 | 52 | 4f83 onWriteRequest 82 (advertising interval) 53 | 4f85 onReadRequest 54 | 55 | 4f82 onWriteRequest 01020304 (pin) 56 | 4f85 onReadRequest 57 | 58 | apply 59 | 60 | 4f82 onWriteRequest 00000000 61 | 4f85 onReadRequest 62 | 63 | set advertising interval 64 | 65 | 4f83 onWriteRequest 02 66 | 4f85 onReadRequest 67 | 4f84 onWriteRequest 0064 68 | 4f85 onReadRequest 69 | 70 | set power 71 | 72 | 73 | 4f83 onWriteRequest 04 74 | 4f85 onReadRequest 75 | 4f84 onWriteRequest ec 76 | 4f85 onReadRequest 77 | 78 | set measured power 79 | 80 | 4f83 onWriteRequest 13 81 | 4f85 onReadRequest 82 | 4f84 onWriteRequest c5 83 | 4f85 onReadRequest 84 | 85 | set minor 86 | 87 | 4f83 onWriteRequest 12 88 | 4f85 onReadRequest 89 | 4f84 onWriteRequest 0014 90 | 4f85 onReadRequest 91 | 92 | set major 93 | 94 | 4f83 onWriteRequest 11 95 | 4f85 onReadRequest 96 | 4f84 onWriteRequest 000a 97 | 4f85 onReadRequest 98 | 99 | set uuid 100 | 101 | 4f83 onWriteRequest 10 102 | 4f85 onReadRequest 103 | 4f84 onWriteRequest b9407f30f5f8466eaff925556b57fe6d 104 | 4f85 onReadRequest 105 | 106 | set name 107 | 108 | 4f83 onWriteRequest 07 109 | 4f85 onReadRequest 110 | 4f84 onWriteRequest 7465737420 111 | 4f85 onReadRequest 112 | 113 | set pin 114 | 115 | 4f82 onWriteRequest 00000000 116 | 4f85 onReadRequest 117 | 4f83 onWriteRequest 01 118 | 4f85 onReadRequest 119 | 4f84 onWriteRequest 05050505 120 | 4f85 onReadRequest 121 | 122 | lock 123 | 124 | 4f82 onWriteRequest 00000000 125 | 4f85 onReadRequest 126 | 4f83 onWriteRequest 70 127 | 4f85 onReadRequest 128 | -------------------------------------------------------------------------------- /radbeacon/radbeacon-usb.txt: -------------------------------------------------------------------------------- 1 | peripheral with UUID ec9664677b9343c8a87d2a3cdb270175 found 2 | Local Name = RadBeacon USB 3 | Manufacturer Data = 4c0002152f234454cf6d4a0fadf2f4911ba9ffa600010001be 4 | Service Data = 5 | Service UUIDs = 6 | 7 | services and characteristics: 8 | 1800 (Generic Access) 9 | 2a00 (Device Name) (Device Name) 10 | properties read 11 | value 526164426561636f6e20555342 | 'RadBeacon USB' 12 | 2a01 (Appearance) (Appearance) 13 | properties read 14 | value 0000 | '' 15 | 180a (Device Information) 16 | 2a29 (Manufacturer Name String) (Manufacturer Name) 17 | properties read 18 | value 526164697573204e6574776f726b732c20496e632e | 'Radius Networks, Inc.' 19 | 2a24 (Model Number String) (Model Number) 20 | properties read 21 | value 30303031 | '0001' 22 | 2a25 (Serial Number String) (Serial Number) 23 | properties read 24 | value 30303030 | '0000' 25 | 2a26 (Firmware Revision String) (Firmware Revision Number) 26 | properties read 27 | value 312e34 | '1.4' 28 | 0a89139fba2b4003a72d18e332be098c 29 | aaa0 (Device Model) 30 | properties read, write 31 | value 526164426561636f6e20555342000000000000000000000000000000000000000000000000 | 'RadBeacon USB' 32 | aaa1 (Device ID) 33 | properties read 34 | value 303235354433 | '0255D3' 35 | aaa2 (Device Name) 36 | properties read, write 37 | value 526164426561636f6e20555342000000000000000000000000000000000000000000000000 | 'RadBeacon USB' 38 | aaa3 (UUID) 39 | properties read, write 40 | value 2f234454cf6d4a0fadf2f4911ba9ffa6 | '/#DTOmJ-rt&' 41 | aaa4 (Major) 42 | properties read, write 43 | value 0001 | '' 44 | aaa5 (Minor) 45 | properties read, write 46 | value 0001 | '' 47 | aaa6 (Power) 48 | properties read, write 49 | value be | '>' 50 | aaa7 (TX Power) 51 | properties read, write 52 | value 0f | '' 53 | aaa8 (Interval) - LE format 54 | properties read, write 55 | value a000 | ' ' 56 | aaa9 (Result) 57 | properties read, write 58 | value 00000000 | '' 59 | aaaa (New PIN) 60 | properties write 61 | aaab (Action) 62 | properties write 63 | aaac (PIN) - ASCI format 64 | properties write 65 | 66 | 67 | aaa2 onWriteRequest 526164426561636f6e20555342 68 | aaa3 onWriteRequest 2f234454cf6d4a0fadf2f4911ba9ffa6 69 | aaa4 onWriteRequest 0001 70 | aaa5 onWriteRequest 0001 71 | aaa6 onWriteRequest be 72 | aaa7 onWriteRequest 0f 73 | aaa8 onWriteRequest 6000 74 | aaaa onWriteRequest 00000001 75 | aaaa onWriteRequest 30303030 76 | aaa9 onReadRequest 77 | 78 | -------------------------------------------------------------------------------- /radbeacon/test-radbeacon-tag.js: -------------------------------------------------------------------------------- 1 | var async = require('async'); 2 | 3 | var RadBeaconTag = require('./radbeacon-tag'); 4 | 5 | RadBeaconTag.discover(function(radbeaconTag) { 6 | 7 | async.series([ 8 | function(callback) { 9 | radbeaconTag.on('disconnect', function() { 10 | console.log('disconnected!'); 11 | process.exit(0); 12 | }); 13 | 14 | console.log('found: ' + radbeaconTag.toString()); 15 | 16 | console.log('connect'); 17 | radbeaconTag.connect(callback); 18 | }, 19 | function(callback) { 20 | console.log('discoverServicesAndCharacteristics'); 21 | radbeaconTag.discoverServicesAndCharacteristics(callback); 22 | }, 23 | function(callback) { 24 | var pin = parseInt('0000', 10); 25 | 26 | console.log('login'); 27 | radbeaconTag.login(pin, function(success) { 28 | console.log('\tsuccess = ' + success); 29 | 30 | callback(); 31 | }); 32 | }, 33 | function(callback) { 34 | console.log('readName'); 35 | radbeaconTag.readName(function(name) { 36 | console.log('\tname = ' + name); 37 | 38 | // name = 'RadBeacon Tag'; 39 | name = 'RadBeacon Tag'; 40 | 41 | console.log('writeName'); 42 | radbeaconTag.writeName(name, callback); 43 | }); 44 | }, 45 | function(callback) { 46 | console.log('readUuid'); 47 | radbeaconTag.readUuid(function(uuid) { 48 | console.log('\tuuid = ' + uuid); 49 | 50 | // uuid = '2f234454cf6d4a0fadf2f4911ba9ffa6'; 51 | 52 | console.log('writeUuid'); 53 | radbeaconTag.writeUuid(uuid, callback); 54 | }); 55 | }, 56 | function(callback) { 57 | console.log('readMajor'); 58 | radbeaconTag.readMajor(function(major) { 59 | console.log('\tmajor = ' + major + ' (0x' + major.toString(16) + ')'); 60 | 61 | // major = 1; 62 | 63 | console.log('writeMajor'); 64 | radbeaconTag.writeMajor(major, callback); 65 | }); 66 | }, 67 | function(callback) { 68 | console.log('readMinor'); 69 | radbeaconTag.readMinor(function(minor) { 70 | console.log('\tminor = ' + minor + ' (0x' + minor.toString(16) + ')'); 71 | 72 | // minor = 288; 73 | 74 | console.log('writeMinor'); 75 | radbeaconTag.writeMinor(minor, callback); 76 | }); 77 | }, 78 | function(callback) { 79 | console.log('readMeasuredTxPower'); 80 | radbeaconTag.readMeasuredTxPower(function(measuredTxPower) { 81 | console.log('\tmeasured TX Power = ' + measuredTxPower); 82 | 83 | // measuredTxPower = -59; 84 | 85 | console.log('writeMeasuredTxPower'); 86 | radbeaconTag.writeMeasuredTxPower(measuredTxPower, callback); 87 | }); 88 | }, 89 | function(callback) { 90 | console.log('readTxPower'); 91 | radbeaconTag.readTxPower(function(txPower) { 92 | console.log('\ttx power = ' + txPower); 93 | 94 | // txPower = 4; 95 | 96 | console.log('writeTxPower'); 97 | radbeaconTag.writeTxPower(txPower, callback); 98 | }); 99 | }, 100 | function(callback) { 101 | console.log('readAdvertisementInterval'); 102 | radbeaconTag.readAdvertisementInterval(function(advertisementInterval) { 103 | console.log('\tadvertisement interval = ' + advertisementInterval); 104 | 105 | // advertisementInterval = 100; 106 | 107 | console.log('writeAdvertisementInterval'); 108 | radbeaconTag.writeAdvertisementInterval(advertisementInterval, callback); 109 | }); 110 | }, 111 | function(callback) { 112 | console.log('readBatteryLevel'); 113 | radbeaconTag.readBatteryLevel(function(batteryLevel) { 114 | console.log('\tbattery level = ' + batteryLevel); 115 | 116 | callback(); 117 | }); 118 | }, 119 | // function(callback) { 120 | // var currentpin = 0000; 121 | // var newpin = 1234; 122 | 123 | // console.log('writePin'); 124 | // radbeaconTag.writePin(currentpin, newpin, function() { 125 | // callback(); 126 | // }); 127 | // }, 128 | function(callback) { 129 | console.log('disconnect'); 130 | radbeaconTag.disconnect(callback); 131 | } 132 | ]); 133 | }); 134 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var Bleacon = require('./index'); 2 | 3 | Bleacon.startAdvertising('e2c56db5dffb48d2b060d0f5a71096e0', 0, 0, -59); 4 | 5 | Bleacon.on('discover', function(bleacon) { 6 | console.log('bleacon found: ' + JSON.stringify(bleacon)); 7 | }); 8 | 9 | Bleacon.startScanning(/*'e2c56db5dffb48d2b060d0f5a71096e0', 0, 0*/); 10 | 11 | --------------------------------------------------------------------------------