├── .bowerrc ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── bower.json ├── btscale.html ├── btscale.js ├── btscale.min.js ├── build.js ├── build_min.js ├── demo.html ├── gulpfile.js ├── lib ├── constants.d.ts ├── constants.js ├── event_target.d.ts ├── event_target.js ├── packet.d.ts ├── packet.js ├── recorder.d.ts ├── recorder.js ├── scale.d.ts ├── scale.js ├── scale_finder.d.ts ├── scale_finder.js ├── types.d.ts └── types.js ├── package.json ├── src ├── build │ ├── end.frag.js │ └── start.frag.js ├── constants.ts ├── event_target.ts ├── packet.ts ├── recorder.ts ├── scale.ts ├── scale_finder.ts ├── types.ts └── vendor │ └── almond.js ├── test └── rt.ts ├── tsconfig.json ├── tslint.json └── typings ├── assertion-error └── assertion-error.d.ts ├── chai └── chai.d.ts ├── mocha └── mocha.d.ts ├── node └── node.d.ts └── tsd.d.ts /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "src/bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | test/*.js 3 | node_modules 4 | src/bower_components 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Bobby Powers 2 | 3 | Permission to use, copy, modify, and/or distribute this software for 4 | any purpose with or without fee is hereby granted, provided that the 5 | above copyright notice and this permission notice appear in all 6 | copies. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL 9 | WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED 10 | WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE 11 | AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL 12 | DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR 13 | PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 14 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 15 | PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | GULP ?= node_modules/.bin/gulp 3 | TSLINT ?= node_modules/.bin/tslint 4 | MOCHA ?= node_modules/.bin/mocha 5 | BOWER ?= node_modules/.bin/bower 6 | ALMOND ?= src/bower_components/almond/almond.js 7 | 8 | BUILD_DEPS = $(GULP) $(TSLINT) $(MOCHA) $(BOWER) $(ALMOND) 9 | 10 | # quiet output, but allow us to look at what commands are being 11 | # executed by passing 'V=1' to make, without requiring temporarily 12 | # editing the Makefile. 13 | ifneq ($V, 1) 14 | MAKEFLAGS += -s 15 | endif 16 | 17 | # GNU make, you are the worst. 18 | .SUFFIXES: 19 | %: %,v 20 | %: RCS/%,v 21 | %: RCS/% 22 | %: s.% 23 | %: SCCS/s.% 24 | 25 | all: test 26 | 27 | build lib: $(BUILD_DEPS) 28 | @echo " LIB" 29 | $(GULP) lib 30 | 31 | test: $(BUILD_DEPS) 32 | @echo " TEST" 33 | $(GULP) 34 | 35 | dist: test 36 | @echo " DIST" 37 | $(GULP) 38 | 39 | node_modules: package.json 40 | @echo " NPM" 41 | npm install --silent 42 | touch -c $@ 43 | 44 | src/bower_components: node_modules bower.json 45 | @echo " BOWER" 46 | $(BOWER) install --silent 47 | touch -c $@ 48 | 49 | 50 | $(BUILD_DEPS): node_modules src/bower_components 51 | touch -c $@ 52 | 53 | clean: 54 | rm -rf lib test/*.js 55 | find . -name '*~' | xargs rm -f 56 | 57 | distclean: clean 58 | rm -rf node_modules 59 | 60 | .PHONY: all clean distclean dist test 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | btscale - bluetooth LE scales from Javascript 2 | ============================================= 3 | 4 | Uses the `chrome.bluetoothLowEnergy` APIs for accessing the Acaia 5 | scale. Currently tested using mobile-chrome-apps on Android + iOS - 6 | should work on ChromeOS devices as well. Support for this API is 7 | underway for OSX, when completed this library should work on the 8 | desktop as well. 9 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "btscale", 3 | "version": "0.2.0", 4 | "description": "access Bluetooth LE scales from Javascript", 5 | "homepage": "https://github.com/bpowers/btscale", 6 | "authors": [ 7 | "Bobby Powers " 8 | ], 9 | "main": "btscale.min.js", 10 | "keywords": [ 11 | "bluetooth", 12 | "coffee", 13 | "acaia" 14 | ], 15 | "license": "ISC", 16 | "ignore": [ 17 | "**/.*", 18 | "node_modules", 19 | "lib/bower_components", 20 | "test" 21 | ], 22 | "dependencies": { 23 | }, 24 | "devDependencies": { 25 | "almond": "~0.3.1" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /btscale.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /btscale.js: -------------------------------------------------------------------------------- 1 | (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= 0) { 101 | if (handlers.length === 1) 102 | delete this.listeners_[type]; 103 | else 104 | handlers.splice(index, 1); 105 | } 106 | } 107 | }; 108 | BTSEventTarget.prototype.dispatchEvent = function (event) { 109 | if (!this.listeners_) 110 | return true; 111 | var self = this; 112 | event.__defineGetter__('target', function () { 113 | return self; 114 | }); 115 | var type = event.type; 116 | var prevented = 0; 117 | if (type in this.listeners_) { 118 | var handlers = this.listeners_[type].concat(); 119 | for (var i = 0, handler = void 0; (handler = handlers[i]); i++) { 120 | if (handler.handleEvent) 121 | prevented |= (handler.handleEvent.call(handler, event) === false); 122 | else 123 | prevented |= (handler.call(this, event) === false); 124 | } 125 | } 126 | return !prevented && !event.defaultPrevented; 127 | }; 128 | return BTSEventTarget; 129 | })(); 130 | exports.BTSEventTarget = BTSEventTarget; 131 | 132 | },{}],3:[function(require,module,exports){ 133 | 'use strict'; 134 | var constants_1 = require('./constants'); 135 | var MAX_PAYLOAD_LENGTH = 10; 136 | var sequenceId = 0; 137 | function nextSequenceId() { 138 | var next = sequenceId++; 139 | sequenceId &= 0xff; 140 | return next; 141 | } 142 | function setSequenceId(id) { 143 | sequenceId = id & 0xff; 144 | } 145 | exports.setSequenceId = setSequenceId; 146 | function getSequenceId() { 147 | return sequenceId; 148 | } 149 | var Message = (function () { 150 | function Message(type, id, payload) { 151 | this.type = type; 152 | this.id = id; 153 | this.payload = payload; 154 | this.value = null; 155 | if (type === 5) { 156 | var value = ((payload[1] & 0xff) << 8) + (payload[0] & 0xff); 157 | for (var i = 0; i < payload[4]; i++) 158 | value /= 10; 159 | if ((payload[6] & 0x02) === 0x02) 160 | value *= -1; 161 | this.value = value; 162 | } 163 | } 164 | return Message; 165 | })(); 166 | exports.Message = Message; 167 | function encipher(out, input, sequenceId) { 168 | for (var i = 0; i < out.byteLength; i++) { 169 | var offset = (input[i] + sequenceId) & 0xff; 170 | out[i] = constants_1.TABLE1[offset]; 171 | } 172 | } 173 | function decipher(input, sequenceId) { 174 | var result = new Uint8Array(input.byteLength); 175 | for (var i = 0; i < input.byteLength; i++) { 176 | var offset = input[i] & 0xff; 177 | result[i] = (constants_1.TABLE2[offset] - sequenceId) & 0xff; 178 | } 179 | return result; 180 | } 181 | function checksum(data) { 182 | var sum = 0; 183 | for (var i = 0; i < data.length; i++) 184 | sum += data[i]; 185 | return sum & 0xff; 186 | } 187 | function encode(msgType, id, payload) { 188 | if (payload.length > MAX_PAYLOAD_LENGTH) 189 | throw 'payload too long: ' + payload.length; 190 | var buf = new ArrayBuffer(8 + payload.length); 191 | var bytes = new Uint8Array(buf); 192 | var sequenceId = nextSequenceId(); 193 | bytes[0] = constants_1.MAGIC1; 194 | bytes[1] = constants_1.MAGIC2; 195 | bytes[2] = 5 + payload.length; 196 | bytes[3] = msgType; 197 | bytes[4] = sequenceId; 198 | bytes[5] = id; 199 | bytes[6] = payload.length & 0xff; 200 | var payloadOut = new Uint8Array(buf, 7, payload.length); 201 | encipher(payloadOut, payload, sequenceId); 202 | var contentsToChecksum = new Uint8Array(buf, 3, payload.length + 4); 203 | bytes[7 + payload.length] = checksum(contentsToChecksum); 204 | return buf; 205 | } 206 | function decode(data) { 207 | var len = data.byteLength; 208 | if (!len) 209 | return; 210 | var bytes = new Uint8Array(data); 211 | if (len < 8) 212 | throw 'data too short: ' + len; 213 | if (bytes[0] !== constants_1.MAGIC1 && bytes[1] !== constants_1.MAGIC2) 214 | throw "don't have the magic"; 215 | var len1 = bytes[2]; 216 | var contentsToChecksum = new Uint8Array(data.slice(3, len - 1)); 217 | var cs = checksum(contentsToChecksum); 218 | if (bytes[len - 1] !== cs) 219 | throw 'checksum mismatch ' + bytes[len - 1] + ' !== ' + cs; 220 | var msgType = bytes[3]; 221 | var sequenceId = bytes[4]; 222 | var id = bytes[5]; 223 | var len2 = bytes[6]; 224 | if (len1 !== len - 3) 225 | throw 'length mismatch 1 ' + len1 + ' !== ' + (len - 3); 226 | if (len2 !== len - 8) 227 | throw 'length mismatch 2'; 228 | var payloadIn = new Uint8Array(data.slice(7, len - 1)); 229 | var payload = decipher(payloadIn, sequenceId); 230 | return new Message(msgType, id, payload); 231 | } 232 | exports.decode = decode; 233 | function encodeWeight(period, time, type) { 234 | if (period === void 0) { period = 1; } 235 | if (time === void 0) { time = 100; } 236 | if (type === void 0) { type = 1; } 237 | var payload = [period & 0xff, time & 0xff, type & 0xff]; 238 | return encode(4, 0, payload); 239 | } 240 | exports.encodeWeight = encodeWeight; 241 | function encodeTare() { 242 | var payload = [0x0, 0x0]; 243 | return encode(12, 0, payload); 244 | } 245 | exports.encodeTare = encodeTare; 246 | function encodeStartTimer() { 247 | var payload = [0x5]; 248 | return encode(12, 0, payload); 249 | } 250 | exports.encodeStartTimer = encodeStartTimer; 251 | function encodePauseTimer() { 252 | var payload = [0x6]; 253 | return encode(12, 0, payload); 254 | } 255 | exports.encodePauseTimer = encodePauseTimer; 256 | function encodeStopTimer() { 257 | var payload = [0x7]; 258 | return encode(12, 0, payload); 259 | } 260 | exports.encodeStopTimer = encodeStopTimer; 261 | function encodeGetTimer(count) { 262 | if (count === void 0) { count = 20; } 263 | var payload = [0x8, count & 0xff]; 264 | return encode(12, 0, payload); 265 | } 266 | exports.encodeGetTimer = encodeGetTimer; 267 | function encodeGetBattery() { 268 | return encode(2, 0, []); 269 | } 270 | exports.encodeGetBattery = encodeGetBattery; 271 | 272 | },{"./constants":1}],4:[function(require,module,exports){ 273 | 'use strict'; 274 | var Recorder = (function () { 275 | function Recorder(scale) { 276 | this.start = Date.now() / 1000; 277 | this.series = []; 278 | this.scale = scale; 279 | this.recordCb = this.record.bind(this); 280 | this.record(); 281 | scale.addEventListener('weightMeasured', this.recordCb); 282 | } 283 | Recorder.prototype.stop = function () { 284 | this.record(); 285 | this.scale.removeEventListener('weightMeasured', this.recordCb); 286 | this.scale = null; 287 | this.recordCb = null; 288 | return this.series; 289 | }; 290 | Recorder.prototype.record = function () { 291 | var time = Date.now() / 1000 - this.start; 292 | this.series.push([time, this.scale.weight]); 293 | }; 294 | return Recorder; 295 | })(); 296 | exports.Recorder = Recorder; 297 | 298 | },{}],5:[function(require,module,exports){ 299 | 'use strict'; 300 | var __extends = (this && this.__extends) || function (d, b) { 301 | for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; 302 | function __() { this.constructor = d; } 303 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 304 | }; 305 | var constants_1 = require('./constants'); 306 | var event_target_1 = require('./event_target'); 307 | var recorder_1 = require('./recorder'); 308 | var packet = require('./packet'); 309 | var Scale = (function (_super) { 310 | __extends(Scale, _super); 311 | function Scale(device) { 312 | _super.call(this); 313 | this.connected = false; 314 | this.service = null; 315 | this.characteristic = null; 316 | this.weight = null; 317 | this.recorder = null; 318 | this.batteryCb = null; 319 | this.series = null; 320 | this.device = device; 321 | this.name = this.device.name; 322 | console.log('created scale for ' + this.device.address + ' (' + this.device.name + ')'); 323 | this.connect(); 324 | } 325 | Scale.prototype.connect = function () { 326 | var _this = this; 327 | if (this.connected) 328 | return; 329 | var log = console.log.bind(console); 330 | this.device.gatt.connect() 331 | .then(function (server) { 332 | return _this.device.gatt.getPrimaryService(constants_1.SCALE_SERVICE_UUID); 333 | }, function (err) { 334 | console.log('error connecting - ' + err); 335 | return null; 336 | }).then(function (service) { 337 | _this.service = service; 338 | console.log('primary services '); 339 | return service.getCharacteristic(constants_1.SCALE_CHARACTERISTIC_UUID); 340 | }, function (err) { 341 | console.log('primary services ERR - ' + err); 342 | debugger; 343 | }).then(function (characteristic) { 344 | log('Starting notifications...'); 345 | _this.characteristic = characteristic; 346 | return characteristic.startNotifications(); 347 | }, function (err) { 348 | console.log('err getting characteristic'); 349 | debugger; 350 | }).then(function (characteristic) { 351 | characteristic.addEventListener('characteristicvaluechanged', _this.characteristicValueChanged.bind(_this)); 352 | _this.notificationsReady(); 353 | }, function (err) { 354 | log('FAILED: ' + err); 355 | debugger; 356 | }); 357 | }; 358 | Scale.prototype.characteristicValueChanged = function (event) { 359 | var msg = packet.decode(event.target.value.buffer); 360 | if (!msg) { 361 | console.log('characteristic value update, but no message'); 362 | return; 363 | } 364 | if (msg.type === 5) { 365 | var prevWeight = this.weight; 366 | var shouldDispatchChanged = this.weight !== msg.value; 367 | this.weight = msg.value; 368 | var detail = { 'detail': { 'value': msg.value, 'previous': prevWeight } }; 369 | this.dispatchEvent(new CustomEvent('weightMeasured', detail)); 370 | if (shouldDispatchChanged) 371 | this.dispatchEvent(new CustomEvent('weightChanged', detail)); 372 | } 373 | else if (msg.type === 3) { 374 | var cb = this.batteryCb; 375 | this.batteryCb = null; 376 | if (cb) 377 | cb(msg.payload[0] / 100); 378 | } 379 | else { 380 | console.log('non-weight response'); 381 | console.log(msg); 382 | } 383 | }; 384 | Scale.prototype.disconnect = function () { 385 | this.connected = false; 386 | if (this.device) 387 | this.device.gatt.connect(); 388 | }; 389 | Scale.prototype.notificationsReady = function () { 390 | console.log('scale ready'); 391 | this.connected = true; 392 | this.poll(); 393 | setInterval(this.poll.bind(this), 1000); 394 | this.dispatchEvent(new CustomEvent('ready', { 'detail': { 'scale': this } })); 395 | }; 396 | Scale.prototype.tare = function () { 397 | if (!this.connected) 398 | return false; 399 | var msg = packet.encodeTare(); 400 | this.characteristic.writeValue(msg) 401 | .then(function () { 402 | }, function (err) { 403 | console.log('write failed: ' + err); 404 | }); 405 | return true; 406 | }; 407 | Scale.prototype.startTimer = function () { 408 | if (!this.connected) 409 | return false; 410 | var msg = packet.encodeStartTimer(); 411 | this.characteristic.writeValue(msg) 412 | .then(function () { 413 | }, function (err) { 414 | console.log('write failed: ' + err); 415 | }); 416 | return true; 417 | }; 418 | Scale.prototype.pauseTimer = function () { 419 | if (!this.connected) 420 | return false; 421 | var msg = packet.encodePauseTimer(); 422 | this.characteristic.writeValue(msg) 423 | .then(function () { 424 | }, function (err) { 425 | console.log('write failed: ' + err); 426 | }); 427 | return true; 428 | }; 429 | Scale.prototype.stopTimer = function () { 430 | if (!this.connected) 431 | return false; 432 | var msg = packet.encodeStopTimer(); 433 | this.characteristic.writeValue(msg) 434 | .then(function () { 435 | }, function (err) { 436 | console.log('write failed: ' + err); 437 | }); 438 | return true; 439 | }; 440 | ; 441 | Scale.prototype.getTimer = function (count) { 442 | if (!this.connected) 443 | return false; 444 | if (!count) 445 | count = 1; 446 | var msg = packet.encodeGetTimer(count); 447 | this.characteristic.writeValue(msg) 448 | .then(function () { 449 | }, function (err) { 450 | console.log('write failed: ' + err); 451 | }); 452 | return true; 453 | }; 454 | Scale.prototype.getBattery = function (cb) { 455 | if (!this.connected) 456 | return false; 457 | this.batteryCb = cb; 458 | var msg = packet.encodeGetBattery(); 459 | this.characteristic.writeValue(msg) 460 | .then(function () { 461 | }, function (err) { 462 | console.log('write failed: ' + err); 463 | }); 464 | return true; 465 | }; 466 | Scale.prototype.poll = function () { 467 | if (!this.connected) 468 | return false; 469 | var msg = packet.encodeWeight(); 470 | this.characteristic.writeValue(msg) 471 | .then(function () { 472 | }, function (err) { 473 | console.log('write failed: ' + err); 474 | }); 475 | return true; 476 | }; 477 | Scale.prototype.startRecording = function () { 478 | if (this.recorder) 479 | return; 480 | this.recorder = new recorder_1.Recorder(this); 481 | }; 482 | Scale.prototype.stopRecording = function () { 483 | this.series = this.recorder.stop(); 484 | this.recorder = null; 485 | return this.series; 486 | }; 487 | return Scale; 488 | })(event_target_1.BTSEventTarget); 489 | exports.Scale = Scale; 490 | 491 | },{"./constants":1,"./event_target":2,"./packet":3,"./recorder":4}],6:[function(require,module,exports){ 492 | 'use strict'; 493 | var __extends = (this && this.__extends) || function (d, b) { 494 | for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; 495 | function __() { this.constructor = d; } 496 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 497 | }; 498 | var constants_1 = require('./constants'); 499 | var event_target_1 = require('./event_target'); 500 | var scale_1 = require('./scale'); 501 | var bluetooth = navigator.bluetooth; 502 | var ScaleFinder = (function (_super) { 503 | __extends(ScaleFinder, _super); 504 | function ScaleFinder() { 505 | _super.call(this); 506 | this.ready = false; 507 | this.devices = {}; 508 | this.scales = []; 509 | this.adapterState = null; 510 | this.failed = false; 511 | console.log('new ScaleFinder'); 512 | } 513 | ScaleFinder.prototype.deviceAdded = function (device) { 514 | if (device.address in this.devices) { 515 | console.log('WARN: device added that is already known ' + device.address); 516 | return; 517 | } 518 | var scale = new scale_1.Scale(device); 519 | this.devices[device.address] = scale; 520 | this.scales.push(scale); 521 | }; 522 | ScaleFinder.prototype.startDiscovery = function () { 523 | var _this = this; 524 | if (this.failed) 525 | return; 526 | bluetooth.requestDevice({ filters: [{ services: [constants_1.SCALE_SERVICE_UUID] }] }) 527 | .then(function (device) { 528 | _this.deviceAdded(device); 529 | }); 530 | }; 531 | ScaleFinder.prototype.stopDiscovery = function () { 532 | if (this.failed) 533 | return; 534 | }; 535 | return ScaleFinder; 536 | })(event_target_1.BTSEventTarget); 537 | exports.ScaleFinder = ScaleFinder; 538 | if (typeof window !== 'undefined') 539 | window.ScaleFinder = ScaleFinder; 540 | 541 | },{"./constants":1,"./event_target":2,"./scale":5}]},{},[6]); 542 | -------------------------------------------------------------------------------- /btscale.min.js: -------------------------------------------------------------------------------- 1 | !function t(e,n,r){function i(c,s){if(!n[c]){if(!e[c]){var a="function"==typeof require&&require;if(!s&&a)return a(c,!0);if(o)return o(c,!0);var u=new Error("Cannot find module '"+c+"'");throw u.code="MODULE_NOT_FOUND",u}var h=n[c]={exports:{}};e[c][0].call(h.exports,function(t){var n=e[c][1][t];return i(n?n:t)},h,h.exports,t,e,n,r)}return n[c].exports}for(var o="function"==typeof require&&require,c=0;c=0&&(1===n.length?delete this.listeners_[t]:n.splice(r,1))}},t.prototype.dispatchEvent=function(t){if(!this.listeners_)return!0;var e=this;t.__defineGetter__("target",function(){return e});var n=t.type,r=0;if(n in this.listeners_)for(var i=this.listeners_[n].concat(),o=0,c=void 0;c=i[o];o++)r|=c.handleEvent?c.handleEvent.call(c,t)===!1:c.call(this,t)===!1;return!r&&!t.defaultPrevented},t}();n.BTSEventTarget=r},{}],3:[function(t,e,n){"use strict";function r(){var t=E++;return E&=255,t}function i(t){E=255&t}function o(t,e,n){for(var r=0;rw)throw"payload too long: "+n.length;var i=new ArrayBuffer(8+n.length),c=new Uint8Array(i),a=r();c[0]=y.MAGIC1,c[1]=y.MAGIC2,c[2]=5+n.length,c[3]=t,c[4]=a,c[5]=e,c[6]=255&n.length;var u=new Uint8Array(i,7,n.length);o(u,n,a);var h=new Uint8Array(i,3,n.length+4);return c[7+n.length]=s(h),i}function u(t){var e=t.byteLength;if(e){var n=new Uint8Array(t);if(8>e)throw"data too short: "+e;if(n[0]!==y.MAGIC1&&n[1]!==y.MAGIC2)throw"don't have the magic";var r=n[2],i=new Uint8Array(t.slice(3,e-1)),o=s(i);if(n[e-1]!==o)throw"checksum mismatch "+n[e-1]+" !== "+o;var a=n[3],u=n[4],h=n[5],l=n[6];if(r!==e-3)throw"length mismatch 1 "+r+" !== "+(e-3);if(l!==e-8)throw"length mismatch 2";var d=new Uint8Array(t.slice(7,e-1)),f=c(d,u);return new C(a,h,f)}}function h(t,e,n){void 0===t&&(t=1),void 0===e&&(e=100),void 0===n&&(n=1);var r=[255&t,255&e,255&n];return a(4,0,r)}function l(){var t=[0,0];return a(12,0,t)}function d(){var t=[5];return a(12,0,t)}function f(){var t=[6];return a(12,0,t)}function v(){var t=[7];return a(12,0,t)}function p(t){void 0===t&&(t=20);var e=[8,255&t];return a(12,0,e)}function g(){return a(2,0,[])}var y=t("./constants"),w=10,E=0;n.setSequenceId=i;var C=function(){function t(t,e,n){if(this.type=t,this.id=e,this.payload=n,this.value=null,5===t){for(var r=((255&n[1])<<8)+(255&n[0]),i=0;i 6 | 7 | 8 | 9 | 10 | 11 | 12 | Web Bluetooth / Battery Level Sample 13 | 14 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 |

Web Bluetooth / Battery Level Sample

48 |

49 | Available in Chrome 45+ | 50 | View Source | 51 | View on GitHub | 52 | Browse Samples 53 |

54 |

The Web Bluetooth 55 | API discovers and communicates with devices over the Bluetooth 4 wireless 56 | standard using the Generic Attribute Profile (GATT). It is currently only 57 | partially implemented in Chrome OS and Android M behind the experimental flag 58 | chrome://flags/#enable-web-bluetooth. Check out all Web Bluetooth Samples.

60 | 61 | 62 |

This sample illustrates the use of the Web Bluetooth API to retrieve battery 63 | information from a nearby Bluetooth Device advertising Battery information with 64 | Bluetooth Low Energy. You may want to try this demo with the BLE Peripheral 65 | Simulator App from the Google 67 | Play Store.

68 | 69 | 70 | 71 | 98 | 99 |

Live Output

100 |
101 |
102 |
103 |

104 | 
105 | 106 | 107 | 108 | 109 | 110 | 118 | 119 | 120 | 121 | 122 |

JavaScript Snippet

123 | 124 | 125 | 126 |
function onButtonClick() {
127 |   'use strict';
128 | 
129 |   log('Requesting Bluetooth Device...');
130 |   navigator.bluetooth.requestDevice(
131 |     {filters: [{services: ['battery_service']}]})
132 |   .then(device => {
133 |     log('> Found ' + device.name);
134 |     log('Connecting to GATT Server...');
135 |     return device.connectGATT();
136 |   })
137 |   .then(server => {
138 |     log('Getting Battery Service...');
139 |     return server.getPrimaryService('battery_service');
140 |   })
141 |   .then(service => {
142 |     log('Getting Battery Level Characteristic...');
143 |     return service.getCharacteristic('battery_level');
144 |   })
145 |   .then(characteristic => {
146 |     log('Reading Battery Level...');
147 |     return characteristic.readValue();
148 |   })
149 |   .then(buffer => {
150 |     let data = new DataView(buffer);
151 |     let batteryLevel = data.getUint8(0);
152 |     log('> Battery Level is ' + batteryLevel + '%');
153 |   })
154 |   .catch(error => {
155 |     log('Argh! ' + error);
156 |   });
157 | }
158 | 159 | 160 | 161 | 162 | 174 | 175 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var browserify = require('browserify'); 4 | var buffer = require('vinyl-buffer'); 5 | var gulp = require('gulp'); 6 | var gutil = require('gulp-util'); 7 | var lint = require('gulp-tslint'); 8 | var merge = require('merge2'); 9 | var mocha = require('gulp-mocha'); 10 | var rename = require('gulp-rename'); 11 | var source = require('vinyl-source-stream'); 12 | var ts = require('gulp-typescript'); 13 | var uglify = require('gulp-uglify'); 14 | 15 | var project = ts.createProject('tsconfig.json', { 16 | sortOutput: true, 17 | declaration: true, 18 | }); 19 | var testProject = ts.createProject('tsconfig.json', { sortOutput: true }); 20 | 21 | gulp.task('lint', function() { 22 | return gulp.src('src/*.ts') 23 | .pipe(lint()) 24 | .pipe(lint.report('verbose')); 25 | }); 26 | 27 | gulp.task('lib', ['lint'], function() { 28 | var tsLib = gulp.src('src/*.ts') 29 | .pipe(ts(project)); 30 | 31 | return merge(tsLib.js, tsLib.dts) 32 | .pipe(gulp.dest('lib')); 33 | }); 34 | 35 | gulp.task('test', ['lib'], function() { 36 | return gulp.src('test/*.ts') 37 | .pipe(ts(project)).js 38 | .pipe(gulp.dest('test')) 39 | .pipe(mocha()); 40 | }); 41 | 42 | gulp.task('btscale.js', ['lib'], function() { 43 | var b = browserify({ 44 | entries: ['./lib/scale_finder.js'], 45 | builtins: false, 46 | insertGlobalVars: { 47 | // don't do anything when seeing use of 'process' - we 48 | // handle this ourselves. 49 | 'process': function() { return "" }, 50 | 'Buffer': function() { return "" }, 51 | 'buffer': function() { return "" }, 52 | }, 53 | }); 54 | 55 | return b.bundle() 56 | .pipe(source('./lib/scale_finder.js')) 57 | .pipe(buffer()) 58 | // .pipe(uglify()) 59 | .on('error', gutil.log) 60 | .pipe(rename('btscale.js')) 61 | .pipe(gulp.dest('.')); 62 | }); 63 | 64 | gulp.task('btscale.min.js', ['lib'], function() { 65 | var b = browserify({ 66 | entries: ['./lib/scale_finder.js'], 67 | builtins: false, 68 | insertGlobalVars: { 69 | // don't do anything when seeing use of 'process' - we 70 | // handle this ourselves. 71 | 'process': function() { return "" }, 72 | 'Buffer': function() { return "" }, 73 | 'buffer': function() { return "" }, 74 | }, 75 | }); 76 | 77 | return b.bundle() 78 | .pipe(source('./lib/scale_finder.js')) 79 | .pipe(buffer()) 80 | .pipe(uglify()) 81 | .on('error', gutil.log) 82 | .pipe(rename('btscale.min.js')) 83 | .pipe(gulp.dest('.')); 84 | }); 85 | 86 | gulp.task('default', ['test', 'btscale.js', 'btscale.min.js']); 87 | -------------------------------------------------------------------------------- /lib/constants.d.ts: -------------------------------------------------------------------------------- 1 | export declare const enum MessageType { 2 | NONE = 0, 3 | STR = 1, 4 | BATTERY = 2, 5 | BATTERY_RESPONSE = 3, 6 | WEIGHT = 4, 7 | WEIGHT_RESPONSE = 5, 8 | WEIGHT_RESPONSE2 = 6, 9 | TARE = 7, 10 | SOUND = 8, 11 | SOUND_ON = 9, 12 | LIGHT_ON = 10, 13 | FILE = 11, 14 | CUSTOM = 12, 15 | SIZE = 13, 16 | } 17 | export declare const SCALE_SERVICE_UUID: string; 18 | export declare const SCALE_CHARACTERISTIC_UUID: string; 19 | export declare const MAGIC1: number; 20 | export declare const MAGIC2: number; 21 | export declare const TABLE1: number[]; 22 | export declare const TABLE2: number[]; 23 | -------------------------------------------------------------------------------- /lib/constants.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | ; 3 | exports.SCALE_SERVICE_UUID = '00001820-0000-1000-8000-00805f9b34fb'; 4 | exports.SCALE_CHARACTERISTIC_UUID = '00002a80-0000-1000-8000-00805f9b34fb'; 5 | exports.MAGIC1 = 0xdf; 6 | exports.MAGIC2 = 0x78; 7 | exports.TABLE1 = [ 8 | 0x00, 0x76, 0x84, 0x50, 0xDB, 0xE4, 0x6F, 0xB2, 9 | 0xFA, 0xFB, 0x4D, 0x4F, 0x8E, 0x57, 0x8C, 0x5F, 10 | 0x9E, 0xAE, 0xB0, 0xB5, 0x5D, 0x96, 0x15, 0xB9, 11 | 0x0F, 0xFC, 0xFD, 0x70, 0x1B, 0x80, 0xBB, 0xF4, 12 | 0x93, 0xFE, 0xFF, 0x69, 0x68, 0x83, 0xCF, 0xA7, 13 | 0xD2, 0xEB, 0x3C, 0x64, 0x41, 0x77, 0xC6, 0x86, 14 | 0xCB, 0xD3, 0xDD, 0x48, 0xEE, 0xF0, 0x1E, 0x58, 15 | 0x4C, 0x8A, 0x8F, 0xA4, 0x02, 0x4B, 0x06, 0x24, 16 | 0x8D, 0xB7, 0xBF, 0x28, 0x63, 0xAD, 0xB8, 0x56, 17 | 0x89, 0xA0, 0xC4, 0x51, 0xC5, 0x52, 0x27, 0x3D, 18 | 0xC9, 0xD6, 0xDC, 0x42, 0x2C, 0xD7, 0xE6, 0xEF, 19 | 0xF9, 0x35, 0xD9, 0xBC, 0x7A, 0x1F, 0x43, 0x6C, 20 | 0x36, 0x38, 0x07, 0x94, 0x98, 0xD8, 0xE3, 0xB6, 21 | 0x53, 0x3F, 0x0C, 0x92, 0x9A, 0xC2, 0xD1, 0xD5, 22 | 0x34, 0x1D, 0x62, 0xA9, 0x20, 0x7E, 0xAC, 0x09, 23 | 0x5E, 0x59, 0x31, 0x9C, 0xA3, 0x97, 0xB3, 0x74, 24 | 0xC1, 0xED, 0xF2, 0x10, 0x2E, 0x4A, 0xE1, 0x23, 25 | 0x2B, 0x81, 0xF7, 0x61, 0x19, 0x08, 0x1A, 0x39, 26 | 0x65, 0x3E, 0x73, 0x3B, 0x7B, 0x0B, 0x67, 0x04, 27 | 0x6A, 0x22, 0x46, 0x0E, 0x55, 0x66, 0x54, 0x01, 28 | 0x45, 0x6B, 0x32, 0x8B, 0xAB, 0x18, 0xBA, 0xCC, 29 | 0xD4, 0x26, 0xE2, 0xE7, 0x1C, 0x44, 0x14, 0x95, 30 | 0x99, 0x85, 0xDA, 0x4E, 0x6E, 0xE0, 0xE8, 0x37, 31 | 0xBE, 0xF3, 0x7F, 0xDF, 0xF6, 0xF8, 0x2D, 0x30, 32 | 0x21, 0x13, 0x17, 0x0D, 0x16, 0x25, 0x5B, 0x33, 33 | 0x11, 0x5C, 0x7C, 0x87, 0xA1, 0xBD, 0x05, 0x90, 34 | 0x9F, 0xA6, 0x6D, 0xB4, 0xC7, 0xCA, 0xC3, 0x12, 35 | 0x03, 0xE5, 0xDE, 0xE9, 0x9B, 0x88, 0x2F, 0xEA, 36 | 0xEC, 0xC8, 0x29, 0x71, 0x49, 0x5A, 0x72, 0x47, 37 | 0x7D, 0xA2, 0xA5, 0x91, 0xAF, 0xB1, 0x0A, 0xCD, 38 | 0x60, 0xC0, 0x9D, 0x78, 0xCE, 0xD0, 0x79, 0x3A, 39 | 0xAA, 0xA8, 0x2A, 0x40, 0xF1, 0x75, 0xF5, 0x82, 40 | ]; 41 | exports.TABLE2 = [ 42 | 0x00, 0x9F, 0x3C, 0xD8, 0x97, 0xCE, 0x3E, 0x62, 43 | 0x8D, 0x77, 0xEE, 0x95, 0x6A, 0xC3, 0x9B, 0x18, 44 | 0x83, 0xC8, 0xD7, 0xC1, 0xAE, 0x16, 0xC4, 0xC2, 45 | 0xA5, 0x8C, 0x8E, 0x1C, 0xAC, 0x71, 0x36, 0x5D, 46 | 0x74, 0xC0, 0x99, 0x87, 0x3F, 0xC5, 0xA9, 0x4E, 47 | 0x43, 0xE2, 0xFA, 0x88, 0x54, 0xBE, 0x84, 0xDE, 48 | 0xBF, 0x7A, 0xA2, 0xC7, 0x70, 0x59, 0x60, 0xB7, 49 | 0x61, 0x8F, 0xF7, 0x93, 0x2A, 0x4F, 0x91, 0x69, 50 | 0xFB, 0x2C, 0x53, 0x5E, 0xAD, 0xA0, 0x9A, 0xE7, 51 | 0x33, 0xE4, 0x85, 0x3D, 0x38, 0x0A, 0xB3, 0x0B, 52 | 0x03, 0x4B, 0x4D, 0x68, 0x9E, 0x9C, 0x47, 0x0D, 53 | 0x37, 0x79, 0xE5, 0xC6, 0xC9, 0x14, 0x78, 0x0F, 54 | 0xF0, 0x8B, 0x72, 0x44, 0x2B, 0x90, 0x9D, 0x96, 55 | 0x24, 0x23, 0x98, 0xA1, 0x5F, 0xD2, 0xB4, 0x06, 56 | 0x1B, 0xE3, 0xE6, 0x92, 0x7F, 0xFD, 0x01, 0x2D, 57 | 0xF3, 0xF6, 0x5C, 0x94, 0xCA, 0xE8, 0x75, 0xBA, 58 | 0x1D, 0x89, 0xFF, 0x25, 0x02, 0xB1, 0x2F, 0xCB, 59 | 0xDD, 0x48, 0x39, 0xA3, 0x0E, 0x40, 0x0C, 0x3A, 60 | 0xCF, 0xEB, 0x6B, 0x20, 0x63, 0xAF, 0x15, 0x7D, 61 | 0x64, 0xB0, 0x6C, 0xDC, 0x7B, 0xF2, 0x10, 0xD0, 62 | 0x49, 0xCC, 0xE9, 0x7C, 0x3B, 0xEA, 0xD1, 0x27, 63 | 0xF9, 0x73, 0xF8, 0xA4, 0x76, 0x45, 0x11, 0xEC, 64 | 0x12, 0xED, 0x07, 0x7E, 0xD3, 0x13, 0x67, 0x41, 65 | 0x46, 0x17, 0xA6, 0x1E, 0x5B, 0xCD, 0xB8, 0x42, 66 | 0xF1, 0x80, 0x6D, 0xD6, 0x4A, 0x4C, 0x2E, 0xD4, 67 | 0xE1, 0x50, 0xD5, 0x30, 0xA7, 0xEF, 0xF4, 0x26, 68 | 0xF5, 0x6E, 0x28, 0x31, 0xA8, 0x6F, 0x51, 0x55, 69 | 0x65, 0x5A, 0xB2, 0x04, 0x52, 0x32, 0xDA, 0xBB, 70 | 0xB5, 0x86, 0xAA, 0x66, 0x05, 0xD9, 0x56, 0xAB, 71 | 0xB6, 0xDB, 0xDF, 0x29, 0xE0, 0x81, 0x34, 0x57, 72 | 0x35, 0xFC, 0x82, 0xB9, 0x1F, 0xFE, 0xBC, 0x8A, 73 | 0xBD, 0x58, 0x08, 0x09, 0x19, 0x1A, 0x21, 0x22, 74 | ]; 75 | -------------------------------------------------------------------------------- /lib/event_target.d.ts: -------------------------------------------------------------------------------- 1 | export declare class BTSEventTarget { 2 | listeners_: any; 3 | addEventListener(type: string, handler: Function): void; 4 | removeEventListener(type: string, handler: Function): void; 5 | dispatchEvent(event: any): boolean; 6 | } 7 | -------------------------------------------------------------------------------- /lib/event_target.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var BTSEventTarget = (function () { 3 | function BTSEventTarget() { 4 | } 5 | BTSEventTarget.prototype.addEventListener = function (type, handler) { 6 | if (!this.listeners_) 7 | this.listeners_ = Object.create(null); 8 | if (!(type in this.listeners_)) { 9 | this.listeners_[type] = [handler]; 10 | } 11 | else { 12 | var handlers = this.listeners_[type]; 13 | if (handlers.indexOf(handler) < 0) 14 | handlers.push(handler); 15 | } 16 | }; 17 | BTSEventTarget.prototype.removeEventListener = function (type, handler) { 18 | if (!this.listeners_) 19 | return; 20 | if (type in this.listeners_) { 21 | var handlers = this.listeners_[type]; 22 | var index = handlers.indexOf(handler); 23 | if (index >= 0) { 24 | if (handlers.length === 1) 25 | delete this.listeners_[type]; 26 | else 27 | handlers.splice(index, 1); 28 | } 29 | } 30 | }; 31 | BTSEventTarget.prototype.dispatchEvent = function (event) { 32 | if (!this.listeners_) 33 | return true; 34 | var self = this; 35 | event.__defineGetter__('target', function () { 36 | return self; 37 | }); 38 | var type = event.type; 39 | var prevented = 0; 40 | if (type in this.listeners_) { 41 | var handlers = this.listeners_[type].concat(); 42 | for (var i = 0, handler = void 0; (handler = handlers[i]); i++) { 43 | if (handler.handleEvent) 44 | prevented |= (handler.handleEvent.call(handler, event) === false); 45 | else 46 | prevented |= (handler.call(this, event) === false); 47 | } 48 | } 49 | return !prevented && !event.defaultPrevented; 50 | }; 51 | return BTSEventTarget; 52 | })(); 53 | exports.BTSEventTarget = BTSEventTarget; 54 | -------------------------------------------------------------------------------- /lib/packet.d.ts: -------------------------------------------------------------------------------- 1 | import { MessageType } from './constants'; 2 | export declare function setSequenceId(id: number): void; 3 | export declare class Message { 4 | type: MessageType; 5 | id: number; 6 | payload: Uint8Array; 7 | value: number; 8 | constructor(type: MessageType, id: number, payload: Uint8Array); 9 | } 10 | export declare function decode(data: ArrayBuffer): Message; 11 | export declare function encodeWeight(period?: number, time?: number, type?: number): ArrayBuffer; 12 | export declare function encodeTare(): ArrayBuffer; 13 | export declare function encodeStartTimer(): ArrayBuffer; 14 | export declare function encodePauseTimer(): ArrayBuffer; 15 | export declare function encodeStopTimer(): ArrayBuffer; 16 | export declare function encodeGetTimer(count?: number): ArrayBuffer; 17 | export declare function encodeGetBattery(): ArrayBuffer; 18 | -------------------------------------------------------------------------------- /lib/packet.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var constants_1 = require('./constants'); 3 | var MAX_PAYLOAD_LENGTH = 10; 4 | var sequenceId = 0; 5 | function nextSequenceId() { 6 | var next = sequenceId++; 7 | sequenceId &= 0xff; 8 | return next; 9 | } 10 | function setSequenceId(id) { 11 | sequenceId = id & 0xff; 12 | } 13 | exports.setSequenceId = setSequenceId; 14 | function getSequenceId() { 15 | return sequenceId; 16 | } 17 | var Message = (function () { 18 | function Message(type, id, payload) { 19 | this.type = type; 20 | this.id = id; 21 | this.payload = payload; 22 | this.value = null; 23 | if (type === 5) { 24 | var value = ((payload[1] & 0xff) << 8) + (payload[0] & 0xff); 25 | for (var i = 0; i < payload[4]; i++) 26 | value /= 10; 27 | if ((payload[6] & 0x02) === 0x02) 28 | value *= -1; 29 | this.value = value; 30 | } 31 | } 32 | return Message; 33 | })(); 34 | exports.Message = Message; 35 | function encipher(out, input, sequenceId) { 36 | for (var i = 0; i < out.byteLength; i++) { 37 | var offset = (input[i] + sequenceId) & 0xff; 38 | out[i] = constants_1.TABLE1[offset]; 39 | } 40 | } 41 | function decipher(input, sequenceId) { 42 | var result = new Uint8Array(input.byteLength); 43 | for (var i = 0; i < input.byteLength; i++) { 44 | var offset = input[i] & 0xff; 45 | result[i] = (constants_1.TABLE2[offset] - sequenceId) & 0xff; 46 | } 47 | return result; 48 | } 49 | function checksum(data) { 50 | var sum = 0; 51 | for (var i = 0; i < data.length; i++) 52 | sum += data[i]; 53 | return sum & 0xff; 54 | } 55 | function encode(msgType, id, payload) { 56 | if (payload.length > MAX_PAYLOAD_LENGTH) 57 | throw 'payload too long: ' + payload.length; 58 | var buf = new ArrayBuffer(8 + payload.length); 59 | var bytes = new Uint8Array(buf); 60 | var sequenceId = nextSequenceId(); 61 | bytes[0] = constants_1.MAGIC1; 62 | bytes[1] = constants_1.MAGIC2; 63 | bytes[2] = 5 + payload.length; 64 | bytes[3] = msgType; 65 | bytes[4] = sequenceId; 66 | bytes[5] = id; 67 | bytes[6] = payload.length & 0xff; 68 | var payloadOut = new Uint8Array(buf, 7, payload.length); 69 | encipher(payloadOut, payload, sequenceId); 70 | var contentsToChecksum = new Uint8Array(buf, 3, payload.length + 4); 71 | bytes[7 + payload.length] = checksum(contentsToChecksum); 72 | return buf; 73 | } 74 | function decode(data) { 75 | var len = data.byteLength; 76 | if (!len) 77 | return; 78 | var bytes = new Uint8Array(data); 79 | if (len < 8) 80 | throw 'data too short: ' + len; 81 | if (bytes[0] !== constants_1.MAGIC1 && bytes[1] !== constants_1.MAGIC2) 82 | throw "don't have the magic"; 83 | var len1 = bytes[2]; 84 | var contentsToChecksum = new Uint8Array(data.slice(3, len - 1)); 85 | var cs = checksum(contentsToChecksum); 86 | if (bytes[len - 1] !== cs) 87 | throw 'checksum mismatch ' + bytes[len - 1] + ' !== ' + cs; 88 | var msgType = bytes[3]; 89 | var sequenceId = bytes[4]; 90 | var id = bytes[5]; 91 | var len2 = bytes[6]; 92 | if (len1 !== len - 3) 93 | throw 'length mismatch 1 ' + len1 + ' !== ' + (len - 3); 94 | if (len2 !== len - 8) 95 | throw 'length mismatch 2'; 96 | var payloadIn = new Uint8Array(data.slice(7, len - 1)); 97 | var payload = decipher(payloadIn, sequenceId); 98 | return new Message(msgType, id, payload); 99 | } 100 | exports.decode = decode; 101 | function encodeWeight(period, time, type) { 102 | if (period === void 0) { period = 1; } 103 | if (time === void 0) { time = 100; } 104 | if (type === void 0) { type = 1; } 105 | var payload = [period & 0xff, time & 0xff, type & 0xff]; 106 | return encode(4, 0, payload); 107 | } 108 | exports.encodeWeight = encodeWeight; 109 | function encodeTare() { 110 | var payload = [0x0, 0x0]; 111 | return encode(12, 0, payload); 112 | } 113 | exports.encodeTare = encodeTare; 114 | function encodeStartTimer() { 115 | var payload = [0x5]; 116 | return encode(12, 0, payload); 117 | } 118 | exports.encodeStartTimer = encodeStartTimer; 119 | function encodePauseTimer() { 120 | var payload = [0x6]; 121 | return encode(12, 0, payload); 122 | } 123 | exports.encodePauseTimer = encodePauseTimer; 124 | function encodeStopTimer() { 125 | var payload = [0x7]; 126 | return encode(12, 0, payload); 127 | } 128 | exports.encodeStopTimer = encodeStopTimer; 129 | function encodeGetTimer(count) { 130 | if (count === void 0) { count = 20; } 131 | var payload = [0x8, count & 0xff]; 132 | return encode(12, 0, payload); 133 | } 134 | exports.encodeGetTimer = encodeGetTimer; 135 | function encodeGetBattery() { 136 | return encode(2, 0, []); 137 | } 138 | exports.encodeGetBattery = encodeGetBattery; 139 | -------------------------------------------------------------------------------- /lib/recorder.d.ts: -------------------------------------------------------------------------------- 1 | import { IScale } from './types'; 2 | export declare class Recorder { 3 | start: number; 4 | series: Array<[number, number]>; 5 | scale: IScale; 6 | recordCb: any; 7 | constructor(scale: IScale); 8 | stop(): Array<[number, number]>; 9 | record(): void; 10 | } 11 | -------------------------------------------------------------------------------- /lib/recorder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var Recorder = (function () { 3 | function Recorder(scale) { 4 | this.start = Date.now() / 1000; 5 | this.series = []; 6 | this.scale = scale; 7 | this.recordCb = this.record.bind(this); 8 | this.record(); 9 | scale.addEventListener('weightMeasured', this.recordCb); 10 | } 11 | Recorder.prototype.stop = function () { 12 | this.record(); 13 | this.scale.removeEventListener('weightMeasured', this.recordCb); 14 | this.scale = null; 15 | this.recordCb = null; 16 | return this.series; 17 | }; 18 | Recorder.prototype.record = function () { 19 | var time = Date.now() / 1000 - this.start; 20 | this.series.push([time, this.scale.weight]); 21 | }; 22 | return Recorder; 23 | })(); 24 | exports.Recorder = Recorder; 25 | -------------------------------------------------------------------------------- /lib/scale.d.ts: -------------------------------------------------------------------------------- 1 | import { BTSEventTarget } from './event_target'; 2 | import { Recorder } from './recorder'; 3 | export declare class Scale extends BTSEventTarget { 4 | connected: boolean; 5 | name: string; 6 | device: any; 7 | service: any; 8 | characteristic: any; 9 | weight: number; 10 | recorder: Recorder; 11 | batteryCb: Function; 12 | series: Array<[number, number]>; 13 | constructor(device: any); 14 | connect(): void; 15 | characteristicValueChanged(event: any): void; 16 | disconnect(): void; 17 | notificationsReady(): void; 18 | tare(): boolean; 19 | startTimer(): boolean; 20 | pauseTimer(): boolean; 21 | stopTimer(): boolean; 22 | getTimer(count: number): boolean; 23 | getBattery(cb: Function): boolean; 24 | poll(): boolean; 25 | startRecording(): void; 26 | stopRecording(): Array<[number, number]>; 27 | } 28 | -------------------------------------------------------------------------------- /lib/scale.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var __extends = (this && this.__extends) || function (d, b) { 3 | for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; 4 | function __() { this.constructor = d; } 5 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 6 | }; 7 | var constants_1 = require('./constants'); 8 | var event_target_1 = require('./event_target'); 9 | var recorder_1 = require('./recorder'); 10 | var packet = require('./packet'); 11 | var Scale = (function (_super) { 12 | __extends(Scale, _super); 13 | function Scale(device) { 14 | _super.call(this); 15 | this.connected = false; 16 | this.service = null; 17 | this.characteristic = null; 18 | this.weight = null; 19 | this.recorder = null; 20 | this.batteryCb = null; 21 | this.series = null; 22 | this.device = device; 23 | this.name = this.device.name; 24 | console.log('created scale for ' + this.device.address + ' (' + this.device.name + ')'); 25 | this.connect(); 26 | } 27 | Scale.prototype.connect = function () { 28 | var _this = this; 29 | if (this.connected) 30 | return; 31 | var log = console.log.bind(console); 32 | this.device.gatt.connect() 33 | .then(function (server) { 34 | return _this.device.gatt.getPrimaryService(constants_1.SCALE_SERVICE_UUID); 35 | }, function (err) { 36 | console.log('error connecting - ' + err); 37 | return null; 38 | }).then(function (service) { 39 | _this.service = service; 40 | console.log('primary services '); 41 | return service.getCharacteristic(constants_1.SCALE_CHARACTERISTIC_UUID); 42 | }, function (err) { 43 | console.log('primary services ERR - ' + err); 44 | debugger; 45 | }).then(function (characteristic) { 46 | log('Starting notifications...'); 47 | _this.characteristic = characteristic; 48 | return characteristic.startNotifications(); 49 | }, function (err) { 50 | console.log('err getting characteristic'); 51 | debugger; 52 | }).then(function (characteristic) { 53 | characteristic.addEventListener('characteristicvaluechanged', _this.characteristicValueChanged.bind(_this)); 54 | _this.notificationsReady(); 55 | }, function (err) { 56 | log('FAILED: ' + err); 57 | debugger; 58 | }); 59 | }; 60 | Scale.prototype.characteristicValueChanged = function (event) { 61 | var msg = packet.decode(event.target.value.buffer); 62 | if (!msg) { 63 | console.log('characteristic value update, but no message'); 64 | return; 65 | } 66 | if (msg.type === 5) { 67 | var prevWeight = this.weight; 68 | var shouldDispatchChanged = this.weight !== msg.value; 69 | this.weight = msg.value; 70 | var detail = { 'detail': { 'value': msg.value, 'previous': prevWeight } }; 71 | this.dispatchEvent(new CustomEvent('weightMeasured', detail)); 72 | if (shouldDispatchChanged) 73 | this.dispatchEvent(new CustomEvent('weightChanged', detail)); 74 | } 75 | else if (msg.type === 3) { 76 | var cb = this.batteryCb; 77 | this.batteryCb = null; 78 | if (cb) 79 | cb(msg.payload[0] / 100); 80 | } 81 | else { 82 | console.log('non-weight response'); 83 | console.log(msg); 84 | } 85 | }; 86 | Scale.prototype.disconnect = function () { 87 | this.connected = false; 88 | if (this.device) 89 | this.device.gatt.connect(); 90 | }; 91 | Scale.prototype.notificationsReady = function () { 92 | console.log('scale ready'); 93 | this.connected = true; 94 | this.poll(); 95 | setInterval(this.poll.bind(this), 1000); 96 | this.dispatchEvent(new CustomEvent('ready', { 'detail': { 'scale': this } })); 97 | }; 98 | Scale.prototype.tare = function () { 99 | if (!this.connected) 100 | return false; 101 | var msg = packet.encodeTare(); 102 | this.characteristic.writeValue(msg) 103 | .then(function () { 104 | }, function (err) { 105 | console.log('write failed: ' + err); 106 | }); 107 | return true; 108 | }; 109 | Scale.prototype.startTimer = function () { 110 | if (!this.connected) 111 | return false; 112 | var msg = packet.encodeStartTimer(); 113 | this.characteristic.writeValue(msg) 114 | .then(function () { 115 | }, function (err) { 116 | console.log('write failed: ' + err); 117 | }); 118 | return true; 119 | }; 120 | Scale.prototype.pauseTimer = function () { 121 | if (!this.connected) 122 | return false; 123 | var msg = packet.encodePauseTimer(); 124 | this.characteristic.writeValue(msg) 125 | .then(function () { 126 | }, function (err) { 127 | console.log('write failed: ' + err); 128 | }); 129 | return true; 130 | }; 131 | Scale.prototype.stopTimer = function () { 132 | if (!this.connected) 133 | return false; 134 | var msg = packet.encodeStopTimer(); 135 | this.characteristic.writeValue(msg) 136 | .then(function () { 137 | }, function (err) { 138 | console.log('write failed: ' + err); 139 | }); 140 | return true; 141 | }; 142 | ; 143 | Scale.prototype.getTimer = function (count) { 144 | if (!this.connected) 145 | return false; 146 | if (!count) 147 | count = 1; 148 | var msg = packet.encodeGetTimer(count); 149 | this.characteristic.writeValue(msg) 150 | .then(function () { 151 | }, function (err) { 152 | console.log('write failed: ' + err); 153 | }); 154 | return true; 155 | }; 156 | Scale.prototype.getBattery = function (cb) { 157 | if (!this.connected) 158 | return false; 159 | this.batteryCb = cb; 160 | var msg = packet.encodeGetBattery(); 161 | this.characteristic.writeValue(msg) 162 | .then(function () { 163 | }, function (err) { 164 | console.log('write failed: ' + err); 165 | }); 166 | return true; 167 | }; 168 | Scale.prototype.poll = function () { 169 | if (!this.connected) 170 | return false; 171 | var msg = packet.encodeWeight(); 172 | this.characteristic.writeValue(msg) 173 | .then(function () { 174 | }, function (err) { 175 | console.log('write failed: ' + err); 176 | }); 177 | return true; 178 | }; 179 | Scale.prototype.startRecording = function () { 180 | if (this.recorder) 181 | return; 182 | this.recorder = new recorder_1.Recorder(this); 183 | }; 184 | Scale.prototype.stopRecording = function () { 185 | this.series = this.recorder.stop(); 186 | this.recorder = null; 187 | return this.series; 188 | }; 189 | return Scale; 190 | })(event_target_1.BTSEventTarget); 191 | exports.Scale = Scale; 192 | -------------------------------------------------------------------------------- /lib/scale_finder.d.ts: -------------------------------------------------------------------------------- 1 | import { BTSEventTarget } from './event_target'; 2 | import { Scale } from './scale'; 3 | export interface ScaleMap { 4 | [addr: string]: Scale; 5 | } 6 | export declare class ScaleFinder extends BTSEventTarget { 7 | ready: boolean; 8 | devices: ScaleMap; 9 | scales: Array; 10 | adapterState: any; 11 | failed: boolean; 12 | constructor(); 13 | deviceAdded(device: any): void; 14 | startDiscovery(): void; 15 | stopDiscovery(): void; 16 | } 17 | -------------------------------------------------------------------------------- /lib/scale_finder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var __extends = (this && this.__extends) || function (d, b) { 3 | for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; 4 | function __() { this.constructor = d; } 5 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 6 | }; 7 | var constants_1 = require('./constants'); 8 | var event_target_1 = require('./event_target'); 9 | var scale_1 = require('./scale'); 10 | var bluetooth = navigator.bluetooth; 11 | var ScaleFinder = (function (_super) { 12 | __extends(ScaleFinder, _super); 13 | function ScaleFinder() { 14 | _super.call(this); 15 | this.ready = false; 16 | this.devices = {}; 17 | this.scales = []; 18 | this.adapterState = null; 19 | this.failed = false; 20 | console.log('new ScaleFinder'); 21 | } 22 | ScaleFinder.prototype.deviceAdded = function (device) { 23 | if (device.address in this.devices) { 24 | console.log('WARN: device added that is already known ' + device.address); 25 | return; 26 | } 27 | var scale = new scale_1.Scale(device); 28 | this.devices[device.address] = scale; 29 | this.scales.push(scale); 30 | }; 31 | ScaleFinder.prototype.startDiscovery = function () { 32 | var _this = this; 33 | if (this.failed) 34 | return; 35 | bluetooth.requestDevice({ filters: [{ services: [constants_1.SCALE_SERVICE_UUID] }] }) 36 | .then(function (device) { 37 | _this.deviceAdded(device); 38 | }); 39 | }; 40 | ScaleFinder.prototype.stopDiscovery = function () { 41 | if (this.failed) 42 | return; 43 | }; 44 | return ScaleFinder; 45 | })(event_target_1.BTSEventTarget); 46 | exports.ScaleFinder = ScaleFinder; 47 | if (typeof window !== 'undefined') 48 | window.ScaleFinder = ScaleFinder; 49 | -------------------------------------------------------------------------------- /lib/types.d.ts: -------------------------------------------------------------------------------- 1 | export interface IEventListener { 2 | addEventListener(event: string, handler: Function): void; 3 | removeEventListener(event: string, handler: Function): void; 4 | } 5 | export interface IScale extends IEventListener { 6 | weight: number; 7 | } 8 | -------------------------------------------------------------------------------- /lib/types.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "btscale", 3 | "version": "0.9.0", 4 | "description": "access Bluetooth LE scales from Javascript", 5 | "homepage": "https://github.com/bpowers/btscale", 6 | "keywords": [ 7 | "bluetooth", 8 | "coffee", 9 | "acaia" 10 | ], 11 | "license": "ISC", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/bpowers/btscale.git" 15 | }, 16 | "dependencies": {}, 17 | "devDependencies": { 18 | "bower": "^1.7.2", 19 | "browserify": "^12.0.2", 20 | "chai": "^3.4.1", 21 | "gulp": "^3.9.0", 22 | "gulp-mocha": "^2.2.0", 23 | "gulp-rename": "^1.2.2", 24 | "gulp-requirejs-bp": "^0.1.5", 25 | "gulp-tslint": "^3.6.0", 26 | "gulp-typescript": "^2.9.2", 27 | "gulp-uglify": "^1.5.1", 28 | "gulp-util": "^3.0.7", 29 | "merge2": "^0.3.6", 30 | "mocha": "^2.3.4", 31 | "requirejs": "^2.1.20", 32 | "tsd": "^0.6.5", 33 | "typescript": "^1.6.2", 34 | "uglify-js": "^2.4.24", 35 | "vinyl-buffer": "^1.0.0", 36 | "vinyl-source-stream": "^1.1.0" 37 | }, 38 | "engines": { 39 | "node": ">=4.0.0" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/build/end.frag.js: -------------------------------------------------------------------------------- 1 | 2 | return require('btscale'); 3 | })); 4 | -------------------------------------------------------------------------------- /src/build/start.frag.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | define(factory); 4 | } else { 5 | global.btscale = factory(); 6 | } 7 | }(typeof window !== "undefined" ? window : this, function () { 8 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Bobby Powers. All rights reserved. 2 | // Use of this source code is governed by the MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | 'use strict'; 6 | 7 | 8 | export const enum MessageType { 9 | NONE = 0, 10 | STR = 1, 11 | BATTERY = 2, 12 | BATTERY_RESPONSE = 3, 13 | WEIGHT = 4, 14 | WEIGHT_RESPONSE = 5, 15 | WEIGHT_RESPONSE2 = 6, 16 | TARE = 7, 17 | SOUND = 8, 18 | SOUND_ON = 9, 19 | LIGHT_ON = 10, 20 | FILE = 11, 21 | CUSTOM = 12, 22 | SIZE = 13, 23 | }; 24 | 25 | export const SCALE_SERVICE_UUID = '00001820-0000-1000-8000-00805f9b34fb'; 26 | export const SCALE_CHARACTERISTIC_UUID = '00002a80-0000-1000-8000-00805f9b34fb'; 27 | 28 | export const MAGIC1 = 0xdf; 29 | export const MAGIC2 = 0x78; 30 | 31 | // used for encoding packets going to the scale 32 | export const TABLE1 = [ 33 | 0x00, 0x76, 0x84, 0x50, 0xDB, 0xE4, 0x6F, 0xB2, 34 | 0xFA, 0xFB, 0x4D, 0x4F, 0x8E, 0x57, 0x8C, 0x5F, 35 | 0x9E, 0xAE, 0xB0, 0xB5, 0x5D, 0x96, 0x15, 0xB9, 36 | 0x0F, 0xFC, 0xFD, 0x70, 0x1B, 0x80, 0xBB, 0xF4, 37 | 0x93, 0xFE, 0xFF, 0x69, 0x68, 0x83, 0xCF, 0xA7, 38 | 0xD2, 0xEB, 0x3C, 0x64, 0x41, 0x77, 0xC6, 0x86, 39 | 0xCB, 0xD3, 0xDD, 0x48, 0xEE, 0xF0, 0x1E, 0x58, 40 | 0x4C, 0x8A, 0x8F, 0xA4, 0x02, 0x4B, 0x06, 0x24, 41 | 0x8D, 0xB7, 0xBF, 0x28, 0x63, 0xAD, 0xB8, 0x56, 42 | 0x89, 0xA0, 0xC4, 0x51, 0xC5, 0x52, 0x27, 0x3D, 43 | 0xC9, 0xD6, 0xDC, 0x42, 0x2C, 0xD7, 0xE6, 0xEF, 44 | 0xF9, 0x35, 0xD9, 0xBC, 0x7A, 0x1F, 0x43, 0x6C, 45 | 0x36, 0x38, 0x07, 0x94, 0x98, 0xD8, 0xE3, 0xB6, 46 | 0x53, 0x3F, 0x0C, 0x92, 0x9A, 0xC2, 0xD1, 0xD5, 47 | 0x34, 0x1D, 0x62, 0xA9, 0x20, 0x7E, 0xAC, 0x09, 48 | 0x5E, 0x59, 0x31, 0x9C, 0xA3, 0x97, 0xB3, 0x74, 49 | 0xC1, 0xED, 0xF2, 0x10, 0x2E, 0x4A, 0xE1, 0x23, 50 | 0x2B, 0x81, 0xF7, 0x61, 0x19, 0x08, 0x1A, 0x39, 51 | 0x65, 0x3E, 0x73, 0x3B, 0x7B, 0x0B, 0x67, 0x04, 52 | 0x6A, 0x22, 0x46, 0x0E, 0x55, 0x66, 0x54, 0x01, 53 | 0x45, 0x6B, 0x32, 0x8B, 0xAB, 0x18, 0xBA, 0xCC, 54 | 0xD4, 0x26, 0xE2, 0xE7, 0x1C, 0x44, 0x14, 0x95, 55 | 0x99, 0x85, 0xDA, 0x4E, 0x6E, 0xE0, 0xE8, 0x37, 56 | 0xBE, 0xF3, 0x7F, 0xDF, 0xF6, 0xF8, 0x2D, 0x30, 57 | 0x21, 0x13, 0x17, 0x0D, 0x16, 0x25, 0x5B, 0x33, 58 | 0x11, 0x5C, 0x7C, 0x87, 0xA1, 0xBD, 0x05, 0x90, 59 | 0x9F, 0xA6, 0x6D, 0xB4, 0xC7, 0xCA, 0xC3, 0x12, 60 | 0x03, 0xE5, 0xDE, 0xE9, 0x9B, 0x88, 0x2F, 0xEA, 61 | 0xEC, 0xC8, 0x29, 0x71, 0x49, 0x5A, 0x72, 0x47, 62 | 0x7D, 0xA2, 0xA5, 0x91, 0xAF, 0xB1, 0x0A, 0xCD, 63 | 0x60, 0xC0, 0x9D, 0x78, 0xCE, 0xD0, 0x79, 0x3A, 64 | 0xAA, 0xA8, 0x2A, 0x40, 0xF1, 0x75, 0xF5, 0x82, 65 | ]; 66 | 67 | // used for decoding packets coming from the scale 68 | export const TABLE2 = [ 69 | 0x00, 0x9F, 0x3C, 0xD8, 0x97, 0xCE, 0x3E, 0x62, 70 | 0x8D, 0x77, 0xEE, 0x95, 0x6A, 0xC3, 0x9B, 0x18, 71 | 0x83, 0xC8, 0xD7, 0xC1, 0xAE, 0x16, 0xC4, 0xC2, 72 | 0xA5, 0x8C, 0x8E, 0x1C, 0xAC, 0x71, 0x36, 0x5D, 73 | 0x74, 0xC0, 0x99, 0x87, 0x3F, 0xC5, 0xA9, 0x4E, 74 | 0x43, 0xE2, 0xFA, 0x88, 0x54, 0xBE, 0x84, 0xDE, 75 | 0xBF, 0x7A, 0xA2, 0xC7, 0x70, 0x59, 0x60, 0xB7, 76 | 0x61, 0x8F, 0xF7, 0x93, 0x2A, 0x4F, 0x91, 0x69, 77 | 0xFB, 0x2C, 0x53, 0x5E, 0xAD, 0xA0, 0x9A, 0xE7, 78 | 0x33, 0xE4, 0x85, 0x3D, 0x38, 0x0A, 0xB3, 0x0B, 79 | 0x03, 0x4B, 0x4D, 0x68, 0x9E, 0x9C, 0x47, 0x0D, 80 | 0x37, 0x79, 0xE5, 0xC6, 0xC9, 0x14, 0x78, 0x0F, 81 | 0xF0, 0x8B, 0x72, 0x44, 0x2B, 0x90, 0x9D, 0x96, 82 | 0x24, 0x23, 0x98, 0xA1, 0x5F, 0xD2, 0xB4, 0x06, 83 | 0x1B, 0xE3, 0xE6, 0x92, 0x7F, 0xFD, 0x01, 0x2D, 84 | 0xF3, 0xF6, 0x5C, 0x94, 0xCA, 0xE8, 0x75, 0xBA, 85 | 0x1D, 0x89, 0xFF, 0x25, 0x02, 0xB1, 0x2F, 0xCB, 86 | 0xDD, 0x48, 0x39, 0xA3, 0x0E, 0x40, 0x0C, 0x3A, 87 | 0xCF, 0xEB, 0x6B, 0x20, 0x63, 0xAF, 0x15, 0x7D, 88 | 0x64, 0xB0, 0x6C, 0xDC, 0x7B, 0xF2, 0x10, 0xD0, 89 | 0x49, 0xCC, 0xE9, 0x7C, 0x3B, 0xEA, 0xD1, 0x27, 90 | 0xF9, 0x73, 0xF8, 0xA4, 0x76, 0x45, 0x11, 0xEC, 91 | 0x12, 0xED, 0x07, 0x7E, 0xD3, 0x13, 0x67, 0x41, 92 | 0x46, 0x17, 0xA6, 0x1E, 0x5B, 0xCD, 0xB8, 0x42, 93 | 0xF1, 0x80, 0x6D, 0xD6, 0x4A, 0x4C, 0x2E, 0xD4, 94 | 0xE1, 0x50, 0xD5, 0x30, 0xA7, 0xEF, 0xF4, 0x26, 95 | 0xF5, 0x6E, 0x28, 0x31, 0xA8, 0x6F, 0x51, 0x55, 96 | 0x65, 0x5A, 0xB2, 0x04, 0x52, 0x32, 0xDA, 0xBB, 97 | 0xB5, 0x86, 0xAA, 0x66, 0x05, 0xD9, 0x56, 0xAB, 98 | 0xB6, 0xDB, 0xDF, 0x29, 0xE0, 0x81, 0x34, 0x57, 99 | 0x35, 0xFC, 0x82, 0xB9, 0x1F, 0xFE, 0xBC, 0x8A, 100 | 0xBD, 0x58, 0x08, 0x09, 0x19, 0x1A, 0x21, 0x22, 101 | ]; 102 | -------------------------------------------------------------------------------- /src/event_target.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2010 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in {Chromium's} LICENSE file. 4 | 5 | 'use strict'; 6 | 7 | /** 8 | * @fileoverview This contains an implementation of the EventTarget interface 9 | * as defined by DOM Level 2 Events. 10 | */ 11 | 12 | 13 | /** 14 | * Creates a new EventTarget. This class implements the DOM level 2 15 | * EventTarget interface and can be used wherever those are used. 16 | * @constructor 17 | * @implements {EventTarget} 18 | * 19 | * BSD-licensed, taken from Chromium: src/ui/webui/resources/js/cr/event_target.js 20 | */ 21 | export class BTSEventTarget { 22 | listeners_: any; 23 | 24 | /** 25 | * Adds an event listener to the target. 26 | * @param {string} type The name of the event. 27 | * @param {EventListenerType} handler The handler for the event. This is 28 | * called when the event is dispatched. 29 | */ 30 | addEventListener(type: string, handler: Function): void { 31 | if (!this.listeners_) 32 | this.listeners_ = Object.create(null); 33 | if (!(type in this.listeners_)) { 34 | this.listeners_[type] = [handler]; 35 | } else { 36 | let handlers = this.listeners_[type]; 37 | if (handlers.indexOf(handler) < 0) 38 | handlers.push(handler); 39 | } 40 | } 41 | 42 | /** 43 | * Removes an event listener from the target. 44 | * @param {string} type The name of the event. 45 | * @param {EventListenerType} handler The handler for the event. 46 | */ 47 | removeEventListener(type: string, handler: Function): void { 48 | if (!this.listeners_) 49 | return; 50 | if (type in this.listeners_) { 51 | let handlers = this.listeners_[type]; 52 | let index = handlers.indexOf(handler); 53 | if (index >= 0) { 54 | // Clean up if this was the last listener. 55 | if (handlers.length === 1) 56 | delete this.listeners_[type]; 57 | else 58 | handlers.splice(index, 1); 59 | } 60 | } 61 | } 62 | 63 | /** 64 | * Dispatches an event and calls all the listeners that are listening to 65 | * the type of the event. 66 | * @param {!Event} event The event to dispatch. 67 | * @return {boolean} Whether the default action was prevented. If someone 68 | * calls preventDefault on the event object then this returns false. 69 | */ 70 | dispatchEvent(event: any): boolean { 71 | if (!this.listeners_) 72 | return true; 73 | 74 | // Since we are using DOM Event objects we need to override some of the 75 | // properties and methods so that we can emulate this correctly. 76 | let self = this; 77 | event.__defineGetter__('target', function(): any { 78 | return self; 79 | }); 80 | 81 | let type = event.type; 82 | let prevented = 0; 83 | if (type in this.listeners_) { 84 | // Clone to prevent removal during dispatch 85 | let handlers = this.listeners_[type].concat(); 86 | for (let i = 0, handler: any; (handler = handlers[i]); i++) { 87 | if (handler.handleEvent) 88 | prevented |= (handler.handleEvent.call(handler, event) === false); 89 | else 90 | prevented |= (handler.call(this, event) === false); 91 | } 92 | } 93 | 94 | return !prevented && !event.defaultPrevented; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/packet.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Bobby Powers. All rights reserved. 2 | // Use of this source code is governed by the MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | 'use strict'; 6 | 7 | import {MessageType, MAGIC1, MAGIC2, TABLE1, TABLE2} from './constants'; 8 | 9 | // TODO(bp) this is a guess 10 | const MAX_PAYLOAD_LENGTH = 10; 11 | 12 | // packet sequence id in the range of 0-255 (unsigned char) 13 | let sequenceId = 0; 14 | 15 | function nextSequenceId(): number { 16 | let next = sequenceId++; 17 | sequenceId &= 0xff; 18 | 19 | return next; 20 | } 21 | 22 | export function setSequenceId(id: number): void { 23 | sequenceId = id & 0xff; 24 | } 25 | 26 | function getSequenceId(): number { 27 | return sequenceId; 28 | } 29 | 30 | export class Message { 31 | type: MessageType; 32 | id: number; 33 | payload: Uint8Array; 34 | value: number; 35 | 36 | constructor(type: MessageType, id: number, payload: Uint8Array) { 37 | this.type = type; 38 | this.id = id; 39 | this.payload = payload; 40 | this.value = null; 41 | 42 | if (type === MessageType.WEIGHT_RESPONSE) { 43 | let value = ((payload[1] & 0xff) << 8) + (payload[0] & 0xff); 44 | for (let i = 0; i < payload[4]; i++) 45 | value /= 10; 46 | if ((payload[6] & 0x02) === 0x02) 47 | value *= -1; 48 | this.value = value; 49 | } 50 | } 51 | } 52 | 53 | function encipher(out: Uint8Array, input: Array, sequenceId: number): void { 54 | for (let i = 0; i < out.byteLength; i++) { 55 | let offset = (input[i] + sequenceId) & 0xff; 56 | out[i] = TABLE1[offset]; 57 | } 58 | } 59 | 60 | function decipher(input: Uint8Array, sequenceId: number): Uint8Array { 61 | let result = new Uint8Array(input.byteLength); 62 | 63 | for (let i = 0; i < input.byteLength; i++) { 64 | let offset = input[i] & 0xff; 65 | result[i] = (TABLE2[offset] - sequenceId) & 0xff; 66 | } 67 | 68 | return result; 69 | } 70 | 71 | function checksum(data: Uint8Array): number { 72 | let sum = 0; 73 | 74 | for (let i = 0; i < data.length; i++) 75 | sum += data[i]; 76 | 77 | return sum & 0xff; 78 | } 79 | 80 | function encode(msgType: MessageType, id: number, payload: Array): ArrayBuffer { 81 | if (payload.length > MAX_PAYLOAD_LENGTH) 82 | throw 'payload too long: ' + payload.length; 83 | 84 | let buf = new ArrayBuffer(8 + payload.length); 85 | let bytes = new Uint8Array(buf); 86 | 87 | let sequenceId = nextSequenceId(); 88 | 89 | bytes[0] = MAGIC1; 90 | bytes[1] = MAGIC2; 91 | bytes[2] = 5 + payload.length; 92 | bytes[3] = msgType; 93 | bytes[4] = sequenceId; 94 | bytes[5] = id; 95 | bytes[6] = payload.length & 0xff; 96 | 97 | let payloadOut = new Uint8Array(buf, 7, payload.length); 98 | 99 | encipher(payloadOut, payload, sequenceId); 100 | 101 | let contentsToChecksum = new Uint8Array(buf, 3, payload.length + 4); 102 | 103 | bytes[7 + payload.length] = checksum(contentsToChecksum); 104 | 105 | return buf; 106 | } 107 | 108 | export function decode(data: ArrayBuffer): Message { 109 | const len = data.byteLength; 110 | if (!len) 111 | return; 112 | 113 | const bytes = new Uint8Array(data); 114 | 115 | if (len < 8) 116 | throw 'data too short: ' + len; 117 | 118 | if (bytes[0] !== MAGIC1 && bytes[1] !== MAGIC2) 119 | throw "don't have the magic"; 120 | 121 | const len1 = bytes[2]; 122 | 123 | const contentsToChecksum = new Uint8Array(data.slice(3, len - 1)); 124 | 125 | const cs = checksum(contentsToChecksum); 126 | if (bytes[len - 1] !== cs) 127 | throw 'checksum mismatch ' + bytes[len - 1] + ' !== ' + cs; 128 | 129 | const msgType = bytes[3]; 130 | const sequenceId = bytes[4]; 131 | const id = bytes[5]; 132 | const len2 = bytes[6]; 133 | 134 | if (len1 !== len - 3) 135 | throw 'length mismatch 1 ' + len1 + ' !== ' + (len - 3); 136 | if (len2 !== len - 8) 137 | throw 'length mismatch 2'; 138 | 139 | const payloadIn = new Uint8Array(data.slice(7, len - 1)); 140 | const payload = decipher(payloadIn, sequenceId); 141 | 142 | return new Message(msgType, id, payload); 143 | } 144 | 145 | export function encodeWeight(period: number = 1, time: number = 100, type: number = 1): ArrayBuffer { 146 | let payload = [period & 0xff, time & 0xff, type & 0xff]; 147 | 148 | return encode(MessageType.WEIGHT, 0, payload); 149 | } 150 | 151 | export function encodeTare(): ArrayBuffer { 152 | let payload = [0x0, 0x0]; 153 | 154 | return encode(MessageType.CUSTOM, 0, payload); 155 | } 156 | 157 | export function encodeStartTimer(): ArrayBuffer { 158 | let payload = [0x5]; 159 | 160 | return encode(MessageType.CUSTOM, 0, payload); 161 | } 162 | 163 | export function encodePauseTimer(): ArrayBuffer { 164 | let payload = [0x6]; 165 | 166 | return encode(MessageType.CUSTOM, 0, payload); 167 | } 168 | 169 | export function encodeStopTimer(): ArrayBuffer { 170 | let payload = [0x7]; 171 | 172 | return encode(MessageType.CUSTOM, 0, payload); 173 | } 174 | 175 | export function encodeGetTimer(count: number = 20): ArrayBuffer { 176 | let payload = [0x8, count & 0xff]; 177 | 178 | return encode(MessageType.CUSTOM, 0, payload); 179 | } 180 | 181 | export function encodeGetBattery(): ArrayBuffer { 182 | return encode(MessageType.BATTERY, 0, []); 183 | } 184 | -------------------------------------------------------------------------------- /src/recorder.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Bobby Powers. All rights reserved. 2 | // Use of this source code is governed by the MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | 'use strict'; 6 | 7 | import {IScale} from './types'; 8 | 9 | export class Recorder { 10 | start: number; 11 | series: Array<[number, number]>; 12 | scale: IScale; 13 | recordCb: any; 14 | 15 | constructor(scale: IScale) { 16 | this.start = Date.now()/1000; 17 | this.series = []; 18 | this.scale = scale; 19 | // for purposes of removing event listener later 20 | this.recordCb = this.record.bind(this); 21 | 22 | this.record(); 23 | 24 | scale.addEventListener('weightMeasured', this.recordCb); 25 | } 26 | 27 | stop(): Array<[number, number]> { 28 | this.record(); 29 | this.scale.removeEventListener('weightMeasured', this.recordCb); 30 | this.scale = null; 31 | this.recordCb = null; 32 | 33 | return this.series; 34 | } 35 | 36 | record(): void { 37 | let time = Date.now()/1000 - this.start; 38 | this.series.push([time, this.scale.weight]); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/scale.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Bobby Powers. All rights reserved. 2 | // Use of this source code is governed by the MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | 'use strict'; 6 | 7 | import {SCALE_CHARACTERISTIC_UUID, SCALE_SERVICE_UUID, MessageType} from './constants'; 8 | import {BTSEventTarget} from './event_target'; 9 | import {Recorder} from './recorder'; 10 | import * as packet from './packet'; 11 | 12 | declare var chrome: any; 13 | 14 | export class Scale extends BTSEventTarget { 15 | connected: boolean = false; 16 | name: string; 17 | device: any; 18 | service: any = null; 19 | characteristic: any = null; 20 | weight: number = null; 21 | recorder: Recorder = null; 22 | batteryCb: Function = null; 23 | 24 | series: Array<[number, number]> = null; 25 | 26 | constructor(device: any) { 27 | super(); 28 | 29 | this.device = device; 30 | this.name = this.device.name; 31 | 32 | console.log('created scale for ' + this.device.address + ' (' + this.device.name + ')'); 33 | this.connect(); 34 | } 35 | 36 | 37 | connect(): void { 38 | if (this.connected) 39 | return; 40 | 41 | let log = console.log.bind(console); 42 | 43 | this.device.gatt.connect() 44 | .then((server: any) => { 45 | return this.device.gatt.getPrimaryService(SCALE_SERVICE_UUID); 46 | }, (err: any) : any => { 47 | console.log('error connecting - ' + err); 48 | return null; 49 | }).then((service: any) => { 50 | this.service = service; 51 | console.log('primary services '); 52 | return service.getCharacteristic(SCALE_CHARACTERISTIC_UUID); 53 | }, (err: any) => { 54 | console.log('primary services ERR - ' + err); 55 | debugger; 56 | }).then((characteristic: any) => { 57 | log('Starting notifications...'); 58 | this.characteristic = characteristic; 59 | return characteristic.startNotifications(); 60 | }, (err: any) => { 61 | console.log('err getting characteristic'); 62 | debugger; 63 | }).then((characteristic: any) => { 64 | characteristic.addEventListener( 65 | 'characteristicvaluechanged', 66 | this.characteristicValueChanged.bind(this)); 67 | this.notificationsReady(); 68 | }, (err: any) => { 69 | log('FAILED: ' + err); 70 | debugger; 71 | }); 72 | } 73 | 74 | characteristicValueChanged(event: any): void { 75 | let msg = packet.decode(event.target.value.buffer); 76 | if (!msg) { 77 | console.log('characteristic value update, but no message'); 78 | return; 79 | } 80 | 81 | if (msg.type === MessageType.WEIGHT_RESPONSE) { 82 | let prevWeight = this.weight; 83 | let shouldDispatchChanged = this.weight !== msg.value; 84 | this.weight = msg.value; 85 | 86 | let detail = {'detail': {'value': msg.value, 'previous': prevWeight}}; 87 | 88 | // always dispatch a measured event, useful for data 89 | // logging, but also issue a less-noisy weightChanged 90 | // event for UI updates. 91 | this.dispatchEvent(new CustomEvent('weightMeasured', detail)); 92 | if (shouldDispatchChanged) 93 | this.dispatchEvent(new CustomEvent('weightChanged', detail)); 94 | } else if (msg.type === MessageType.BATTERY_RESPONSE) { 95 | let cb = this.batteryCb; 96 | this.batteryCb = null; 97 | if (cb) 98 | cb(msg.payload[0]/100); 99 | } else { 100 | console.log('non-weight response'); 101 | console.log(msg); 102 | } 103 | } 104 | 105 | disconnect(): void { 106 | this.connected = false; 107 | if (this.device) 108 | this.device.gatt.connect(); 109 | } 110 | 111 | notificationsReady(): void { 112 | console.log('scale ready'); 113 | 114 | this.connected = true; 115 | 116 | this.poll(); 117 | setInterval(this.poll.bind(this), 1000); 118 | 119 | this.dispatchEvent(new CustomEvent('ready', {'detail': {'scale': this}})); 120 | } 121 | 122 | tare(): boolean { 123 | if (!this.connected) 124 | return false; 125 | 126 | let msg = packet.encodeTare(); 127 | this.characteristic.writeValue(msg) 128 | .then(() => { 129 | }, (err: any) => { 130 | console.log('write failed: ' + err); 131 | }); 132 | 133 | return true; 134 | } 135 | 136 | startTimer(): boolean { 137 | if (!this.connected) 138 | return false; 139 | 140 | let msg = packet.encodeStartTimer(); 141 | this.characteristic.writeValue(msg) 142 | .then(() => { 143 | }, (err: any) => { 144 | console.log('write failed: ' + err); 145 | }); 146 | 147 | return true; 148 | } 149 | 150 | pauseTimer(): boolean { 151 | if (!this.connected) 152 | return false; 153 | 154 | let msg = packet.encodePauseTimer(); 155 | this.characteristic.writeValue(msg) 156 | .then(() => { 157 | }, (err: any) => { 158 | console.log('write failed: ' + err); 159 | }); 160 | 161 | return true; 162 | } 163 | 164 | stopTimer(): boolean { 165 | if (!this.connected) 166 | return false; 167 | 168 | let msg = packet.encodeStopTimer(); 169 | this.characteristic.writeValue(msg) 170 | .then(() => { 171 | }, (err: any) => { 172 | console.log('write failed: ' + err); 173 | }); 174 | 175 | return true; 176 | }; 177 | 178 | getTimer(count: number): boolean { 179 | if (!this.connected) 180 | return false; 181 | 182 | if (!count) 183 | count = 1; 184 | 185 | let msg = packet.encodeGetTimer(count); 186 | this.characteristic.writeValue(msg) 187 | .then(() => { 188 | }, (err: any) => { 189 | console.log('write failed: ' + err); 190 | }); 191 | 192 | return true; 193 | } 194 | 195 | getBattery(cb: Function): boolean { 196 | if (!this.connected) 197 | return false; 198 | 199 | this.batteryCb = cb; 200 | 201 | let msg = packet.encodeGetBattery(); 202 | this.characteristic.writeValue(msg) 203 | .then(() => { 204 | }, (err: any) => { 205 | console.log('write failed: ' + err); 206 | }); 207 | 208 | return true; 209 | } 210 | 211 | poll(): boolean { 212 | if (!this.connected) 213 | return false; 214 | 215 | let msg = packet.encodeWeight(); 216 | this.characteristic.writeValue(msg) 217 | .then(() => { 218 | }, (err: any) => { 219 | console.log('write failed: ' + err); 220 | }); 221 | 222 | return true; 223 | } 224 | 225 | startRecording(): void { 226 | if (this.recorder) 227 | return; 228 | 229 | this.recorder = new Recorder(this); 230 | } 231 | 232 | stopRecording(): Array<[number, number]> { 233 | this.series = this.recorder.stop(); 234 | this.recorder = null; 235 | 236 | return this.series; 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /src/scale_finder.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Bobby Powers. All rights reserved. 2 | // Use of this source code is governed by the MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | 'use strict'; 6 | 7 | import {SCALE_SERVICE_UUID, SCALE_CHARACTERISTIC_UUID} from './constants'; 8 | import {BTSEventTarget} from './event_target'; 9 | import {Scale} from './scale'; 10 | 11 | declare var chrome: any; 12 | 13 | let bluetooth = (navigator).bluetooth; 14 | 15 | export interface ScaleMap { 16 | [addr: string]: Scale; 17 | } 18 | 19 | export class ScaleFinder extends BTSEventTarget { 20 | ready: boolean = false; 21 | devices: ScaleMap = {}; 22 | scales: Array = []; 23 | adapterState: any = null; 24 | failed: boolean = false; 25 | 26 | constructor() { 27 | super(); 28 | console.log('new ScaleFinder'); 29 | } 30 | 31 | deviceAdded(device: any): void { 32 | if (device.address in this.devices) { 33 | console.log('WARN: device added that is already known ' + device.address); 34 | return; 35 | } 36 | let scale = new Scale(device); 37 | this.devices[device.address] = scale; 38 | this.scales.push(scale); 39 | } 40 | 41 | startDiscovery(): void { 42 | if (this.failed) 43 | return; 44 | 45 | bluetooth.requestDevice( 46 | {filters: [{services: [SCALE_SERVICE_UUID]}]}) 47 | .then((device: any) => { 48 | this.deviceAdded(device); 49 | }); 50 | } 51 | 52 | stopDiscovery(): void { 53 | if (this.failed) 54 | return; 55 | } 56 | } 57 | 58 | // install our Boot method in the global scope 59 | if (typeof window !== 'undefined') 60 | (window).ScaleFinder = ScaleFinder; 61 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Bobby Powers. All rights reserved. 2 | // Use of this source code is governed by the MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | 'use strict'; 6 | 7 | export interface IEventListener { 8 | addEventListener(event: string, handler: Function): void; 9 | removeEventListener(event: string, handler: Function): void; 10 | } 11 | 12 | export interface IScale extends IEventListener { 13 | weight: number; 14 | } 15 | -------------------------------------------------------------------------------- /src/vendor/almond.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license almond 0.3.0 Copyright (c) 2011-2014, The Dojo Foundation All Rights Reserved. 3 | * Available via the MIT or new BSD license. 4 | * see: http://github.com/jrburke/almond for details 5 | */ 6 | //Going sloppy to avoid 'use strict' string cost, but strict practices should 7 | //be followed. 8 | /*jslint sloppy: true */ 9 | /*global setTimeout: false */ 10 | 11 | var requirejs, require, define; 12 | (function (undef) { 13 | var main, req, makeMap, handlers, 14 | defined = {}, 15 | waiting = {}, 16 | config = {}, 17 | defining = {}, 18 | hasOwn = Object.prototype.hasOwnProperty, 19 | aps = [].slice, 20 | jsSuffixRegExp = /\.js$/; 21 | 22 | function hasProp(obj, prop) { 23 | return hasOwn.call(obj, prop); 24 | } 25 | 26 | /** 27 | * Given a relative module name, like ./something, normalize it to 28 | * a real name that can be mapped to a path. 29 | * @param {String} name the relative name 30 | * @param {String} baseName a real name that the name arg is relative 31 | * to. 32 | * @returns {String} normalized name 33 | */ 34 | function normalize(name, baseName) { 35 | var nameParts, nameSegment, mapValue, foundMap, lastIndex, 36 | foundI, foundStarMap, starI, i, j, part, 37 | baseParts = baseName && baseName.split("/"), 38 | map = config.map, 39 | starMap = (map && map['*']) || {}; 40 | 41 | //Adjust any relative paths. 42 | if (name && name.charAt(0) === ".") { 43 | //If have a base name, try to normalize against it, 44 | //otherwise, assume it is a top-level require that will 45 | //be relative to baseUrl in the end. 46 | if (baseName) { 47 | //Convert baseName to array, and lop off the last part, 48 | //so that . matches that "directory" and not name of the baseName's 49 | //module. For instance, baseName of "one/two/three", maps to 50 | //"one/two/three.js", but we want the directory, "one/two" for 51 | //this normalization. 52 | baseParts = baseParts.slice(0, baseParts.length - 1); 53 | name = name.split('/'); 54 | lastIndex = name.length - 1; 55 | 56 | // Node .js allowance: 57 | if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) { 58 | name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, ''); 59 | } 60 | 61 | name = baseParts.concat(name); 62 | 63 | //start trimDots 64 | for (i = 0; i < name.length; i += 1) { 65 | part = name[i]; 66 | if (part === ".") { 67 | name.splice(i, 1); 68 | i -= 1; 69 | } else if (part === "..") { 70 | if (i === 1 && (name[2] === '..' || name[0] === '..')) { 71 | //End of the line. Keep at least one non-dot 72 | //path segment at the front so it can be mapped 73 | //correctly to disk. Otherwise, there is likely 74 | //no path mapping for a path starting with '..'. 75 | //This can still fail, but catches the most reasonable 76 | //uses of .. 77 | break; 78 | } else if (i > 0) { 79 | name.splice(i - 1, 2); 80 | i -= 2; 81 | } 82 | } 83 | } 84 | //end trimDots 85 | 86 | name = name.join("/"); 87 | } else if (name.indexOf('./') === 0) { 88 | // No baseName, so this is ID is resolved relative 89 | // to baseUrl, pull off the leading dot. 90 | name = name.substring(2); 91 | } 92 | } 93 | 94 | //Apply map config if available. 95 | if ((baseParts || starMap) && map) { 96 | nameParts = name.split('/'); 97 | 98 | for (i = nameParts.length; i > 0; i -= 1) { 99 | nameSegment = nameParts.slice(0, i).join("/"); 100 | 101 | if (baseParts) { 102 | //Find the longest baseName segment match in the config. 103 | //So, do joins on the biggest to smallest lengths of baseParts. 104 | for (j = baseParts.length; j > 0; j -= 1) { 105 | mapValue = map[baseParts.slice(0, j).join('/')]; 106 | 107 | //baseName segment has config, find if it has one for 108 | //this name. 109 | if (mapValue) { 110 | mapValue = mapValue[nameSegment]; 111 | if (mapValue) { 112 | //Match, update name to the new value. 113 | foundMap = mapValue; 114 | foundI = i; 115 | break; 116 | } 117 | } 118 | } 119 | } 120 | 121 | if (foundMap) { 122 | break; 123 | } 124 | 125 | //Check for a star map match, but just hold on to it, 126 | //if there is a shorter segment match later in a matching 127 | //config, then favor over this star map. 128 | if (!foundStarMap && starMap && starMap[nameSegment]) { 129 | foundStarMap = starMap[nameSegment]; 130 | starI = i; 131 | } 132 | } 133 | 134 | if (!foundMap && foundStarMap) { 135 | foundMap = foundStarMap; 136 | foundI = starI; 137 | } 138 | 139 | if (foundMap) { 140 | nameParts.splice(0, foundI, foundMap); 141 | name = nameParts.join('/'); 142 | } 143 | } 144 | 145 | return name; 146 | } 147 | 148 | function makeRequire(relName, forceSync) { 149 | return function () { 150 | //A version of a require function that passes a moduleName 151 | //value for items that may need to 152 | //look up paths relative to the moduleName 153 | var args = aps.call(arguments, 0); 154 | 155 | //If first arg is not require('string'), and there is only 156 | //one arg, it is the array form without a callback. Insert 157 | //a null so that the following concat is correct. 158 | if (typeof args[0] !== 'string' && args.length === 1) { 159 | args.push(null); 160 | } 161 | return req.apply(undef, args.concat([relName, forceSync])); 162 | }; 163 | } 164 | 165 | function makeNormalize(relName) { 166 | return function (name) { 167 | return normalize(name, relName); 168 | }; 169 | } 170 | 171 | function makeLoad(depName) { 172 | return function (value) { 173 | defined[depName] = value; 174 | }; 175 | } 176 | 177 | function callDep(name) { 178 | if (hasProp(waiting, name)) { 179 | var args = waiting[name]; 180 | delete waiting[name]; 181 | defining[name] = true; 182 | main.apply(undef, args); 183 | } 184 | 185 | if (!hasProp(defined, name) && !hasProp(defining, name)) { 186 | throw new Error('No ' + name); 187 | } 188 | return defined[name]; 189 | } 190 | 191 | //Turns a plugin!resource to [plugin, resource] 192 | //with the plugin being undefined if the name 193 | //did not have a plugin prefix. 194 | function splitPrefix(name) { 195 | var prefix, 196 | index = name ? name.indexOf('!') : -1; 197 | if (index > -1) { 198 | prefix = name.substring(0, index); 199 | name = name.substring(index + 1, name.length); 200 | } 201 | return [prefix, name]; 202 | } 203 | 204 | /** 205 | * Makes a name map, normalizing the name, and using a plugin 206 | * for normalization if necessary. Grabs a ref to plugin 207 | * too, as an optimization. 208 | */ 209 | makeMap = function (name, relName) { 210 | var plugin, 211 | parts = splitPrefix(name), 212 | prefix = parts[0]; 213 | 214 | name = parts[1]; 215 | 216 | if (prefix) { 217 | prefix = normalize(prefix, relName); 218 | plugin = callDep(prefix); 219 | } 220 | 221 | //Normalize according 222 | if (prefix) { 223 | if (plugin && plugin.normalize) { 224 | name = plugin.normalize(name, makeNormalize(relName)); 225 | } else { 226 | name = normalize(name, relName); 227 | } 228 | } else { 229 | name = normalize(name, relName); 230 | parts = splitPrefix(name); 231 | prefix = parts[0]; 232 | name = parts[1]; 233 | if (prefix) { 234 | plugin = callDep(prefix); 235 | } 236 | } 237 | 238 | //Using ridiculous property names for space reasons 239 | return { 240 | f: prefix ? prefix + '!' + name : name, //fullName 241 | n: name, 242 | pr: prefix, 243 | p: plugin 244 | }; 245 | }; 246 | 247 | function makeConfig(name) { 248 | return function () { 249 | return (config && config.config && config.config[name]) || {}; 250 | }; 251 | } 252 | 253 | handlers = { 254 | require: function (name) { 255 | return makeRequire(name); 256 | }, 257 | exports: function (name) { 258 | var e = defined[name]; 259 | if (typeof e !== 'undefined') { 260 | return e; 261 | } else { 262 | return (defined[name] = {}); 263 | } 264 | }, 265 | module: function (name) { 266 | return { 267 | id: name, 268 | uri: '', 269 | exports: defined[name], 270 | config: makeConfig(name) 271 | }; 272 | } 273 | }; 274 | 275 | main = function (name, deps, callback, relName) { 276 | var cjsModule, depName, ret, map, i, 277 | args = [], 278 | callbackType = typeof callback, 279 | usingExports; 280 | 281 | //Use name if no relName 282 | relName = relName || name; 283 | 284 | //Call the callback to define the module, if necessary. 285 | if (callbackType === 'undefined' || callbackType === 'function') { 286 | //Pull out the defined dependencies and pass the ordered 287 | //values to the callback. 288 | //Default to [require, exports, module] if no deps 289 | deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps; 290 | for (i = 0; i < deps.length; i += 1) { 291 | map = makeMap(deps[i], relName); 292 | depName = map.f; 293 | 294 | //Fast path CommonJS standard dependencies. 295 | if (depName === "require") { 296 | args[i] = handlers.require(name); 297 | } else if (depName === "exports") { 298 | //CommonJS module spec 1.1 299 | args[i] = handlers.exports(name); 300 | usingExports = true; 301 | } else if (depName === "module") { 302 | //CommonJS module spec 1.1 303 | cjsModule = args[i] = handlers.module(name); 304 | } else if (hasProp(defined, depName) || 305 | hasProp(waiting, depName) || 306 | hasProp(defining, depName)) { 307 | args[i] = callDep(depName); 308 | } else if (map.p) { 309 | map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {}); 310 | args[i] = defined[depName]; 311 | } else { 312 | throw new Error(name + ' missing ' + depName); 313 | } 314 | } 315 | 316 | ret = callback ? callback.apply(defined[name], args) : undefined; 317 | 318 | if (name) { 319 | //If setting exports via "module" is in play, 320 | //favor that over return value and exports. After that, 321 | //favor a non-undefined return value over exports use. 322 | if (cjsModule && cjsModule.exports !== undef && 323 | cjsModule.exports !== defined[name]) { 324 | defined[name] = cjsModule.exports; 325 | } else if (ret !== undef || !usingExports) { 326 | //Use the return value from the function. 327 | defined[name] = ret; 328 | } 329 | } 330 | } else if (name) { 331 | //May just be an object definition for the module. Only 332 | //worry about defining if have a module name. 333 | defined[name] = callback; 334 | } 335 | }; 336 | 337 | requirejs = require = req = function (deps, callback, relName, forceSync, alt) { 338 | if (typeof deps === "string") { 339 | if (handlers[deps]) { 340 | //callback in this case is really relName 341 | return handlers[deps](callback); 342 | } 343 | //Just return the module wanted. In this scenario, the 344 | //deps arg is the module name, and second arg (if passed) 345 | //is just the relName. 346 | //Normalize module name, if it contains . or .. 347 | return callDep(makeMap(deps, callback).f); 348 | } else if (!deps.splice) { 349 | //deps is a config object, not an array. 350 | config = deps; 351 | if (config.deps) { 352 | req(config.deps, config.callback); 353 | } 354 | if (!callback) { 355 | return; 356 | } 357 | 358 | if (callback.splice) { 359 | //callback is an array, which means it is a dependency list. 360 | //Adjust args if there are dependencies 361 | deps = callback; 362 | callback = relName; 363 | relName = null; 364 | } else { 365 | deps = undef; 366 | } 367 | } 368 | 369 | //Support require(['a']) 370 | callback = callback || function () {}; 371 | 372 | //If relName is a function, it is an errback handler, 373 | //so remove it. 374 | if (typeof relName === 'function') { 375 | relName = forceSync; 376 | forceSync = alt; 377 | } 378 | 379 | //Simulate async callback; 380 | if (forceSync) { 381 | main(undef, deps, callback, relName); 382 | } else { 383 | //Using a non-zero value because of concern for what old browsers 384 | //do, and latest browsers "upgrade" to 4 if lower value is used: 385 | //http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#dom-windowtimers-settimeout: 386 | //If want a value immediately, use require('id') instead -- something 387 | //that works in almond on the global level, but not guaranteed and 388 | //unlikely to work in other AMD implementations. 389 | setTimeout(function () { 390 | main(undef, deps, callback, relName); 391 | }, 4); 392 | } 393 | 394 | return req; 395 | }; 396 | 397 | /** 398 | * Just drops the config on the floor, but returns req in case 399 | * the config return value is used. 400 | */ 401 | req.config = function (cfg) { 402 | return req(cfg); 403 | }; 404 | 405 | /** 406 | * Expose module registry for debugging and tooling 407 | */ 408 | requirejs._defined = defined; 409 | 410 | define = function (name, deps, callback) { 411 | 412 | //This module may not have dependencies 413 | if (!deps.splice) { 414 | //deps is not an array, so probably means 415 | //an object literal or factory function for 416 | //the value. Adjust args. 417 | callback = deps; 418 | deps = []; 419 | } 420 | 421 | if (!hasProp(defined, name) && !hasProp(waiting, name)) { 422 | waiting[name] = [name, deps, callback]; 423 | } 424 | }; 425 | 426 | define.amd = { 427 | jQuery: true 428 | }; 429 | }()); 430 | -------------------------------------------------------------------------------- /test/rt.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Bobby Powers. All rights reserved. 2 | // Use of this source code is governed by the MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | /// 6 | 7 | 'use strict'; 8 | 9 | import * as chai from 'chai'; 10 | 11 | import {MessageType} from '../lib/constants'; 12 | import * as packet from '../lib/packet'; 13 | 14 | const TARE_PACKET = [0xdf, 0x78, 0x7, 0xc, 0x3, 0x0, 0x2, 0x50, 0x50, 0xb1]; 15 | const WEIGHT_PACKET = [0xdf, 0x78, 0x8, 0x4, 0x2, 0x0, 0x3, 0x50, 0xe3, 0x50, 0x8c]; 16 | const WEIGHT_RESPONSE_PACKET = [0xdf, 0x78, 0xc, 0x5, 0x62, 0x2, 0x7, 0x7, 0x7, 0x7, 0x7, 0x94, 0x98, 0xc, 0xc4]; 17 | 18 | const expect = chai.expect; 19 | 20 | function pack(input: Array): ArrayBuffer { 21 | let buf = new ArrayBuffer(input.length); 22 | let bytes = new Uint8Array(buf); 23 | 24 | for (let i = 0; i < input.length; i++) 25 | bytes[i] = input[i]; 26 | 27 | return buf; 28 | } 29 | 30 | function contentsEqual(msg: ArrayBuffer, ref: Array): void { 31 | let bytes = new Uint8Array(msg); 32 | for (let i = 0; i < msg.byteLength; i++) 33 | expect(bytes[i]).to.equal(ref[i]); 34 | } 35 | 36 | describe('encode', () => { 37 | it('should-encode tare', () => { 38 | packet.setSequenceId(TARE_PACKET[4]); 39 | let encodedMsg = packet.encodeTare(); 40 | 41 | expect(encodedMsg).to.be.ok; 42 | expect(encodedMsg.byteLength).to.equal(TARE_PACKET.length); 43 | contentsEqual(encodedMsg, TARE_PACKET); 44 | }); 45 | 46 | it('should-encode weight', () => { 47 | packet.setSequenceId(WEIGHT_PACKET[4]); 48 | let encodedMsg = packet.encodeWeight(); 49 | 50 | expect(encodedMsg).to.be.ok; 51 | expect(encodedMsg.byteLength).to.equal(WEIGHT_PACKET.length); 52 | contentsEqual(encodedMsg, WEIGHT_PACKET); 53 | }); 54 | }); 55 | 56 | describe('decode', () => { 57 | it('should-decode', () => { 58 | let msg: packet.Message; 59 | 60 | try { 61 | msg = packet.decode(pack(WEIGHT_RESPONSE_PACKET)); 62 | } catch (e) { 63 | console.log(e); 64 | expect(false); 65 | } 66 | 67 | expect(msg).to.be.ok; 68 | expect(msg.type).to.equal(MessageType.WEIGHT_RESPONSE); 69 | expect(msg.value).to.equal(0); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "noImplicitAny": true, 6 | "removeComments": true, 7 | "newLine": "LF" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "align": [true, 4 | "parameters", 5 | "statements"], 6 | "ban": false, 7 | "class-name": true, 8 | "comment-format": [true 9 | ], 10 | "curly": false, 11 | "eofline": true, 12 | "forin": true, 13 | "indent": [true, "tabs"], 14 | "interface-name": false, 15 | "jsdoc-format": true, 16 | "label-position": true, 17 | "label-undefined": true, 18 | "max-line-length": [true, 140], 19 | "member-access": false, 20 | "member-ordering": [true, 21 | "public-before-private", 22 | "static-before-instance", 23 | "variables-before-functions" 24 | ], 25 | "no-any": false, 26 | "no-arg": true, 27 | "no-bitwise": false, 28 | "no-conditional-assignment": false, 29 | "no-console": [true, 30 | "debug", 31 | "info", 32 | "time", 33 | "timeEnd" 34 | ], 35 | "no-construct": true, 36 | "no-constructor-vars": false, 37 | "no-debugger": false, 38 | "no-duplicate-key": true, 39 | "no-shadowed-variable": false, 40 | "no-duplicate-variable": true, 41 | "no-empty": false, 42 | "no-eval": true, 43 | "no-internal-module": true, 44 | "no-require-imports": false, 45 | "no-string-literal": false, 46 | "no-switch-case-fall-through": true, 47 | "no-trailing-comma": false, 48 | "no-trailing-whitespace": true, 49 | "no-unreachable": true, 50 | "no-unused-expression": true, 51 | "no-unused-variable": false, 52 | "no-use-before-declare": true, 53 | "no-var-keyword": true, 54 | "no-var-requires": false, 55 | "one-line": [true, 56 | "check-open-brace", 57 | "check-catch", 58 | "check-whitespace" 59 | ], 60 | "radix": true, 61 | "semicolon": true, 62 | "switch-default": false, 63 | "triple-equals": [true, "allow-null-check"], 64 | "typedef": [true, 65 | "call-signature", 66 | "property-declaration", 67 | "member-variable-declaration" 68 | ], 69 | "typedef-whitespace": [true, { 70 | "call-signature": "nospace", 71 | "index-signature": "nospace", 72 | "parameter": "nospace", 73 | "property-declaration": "nospace", 74 | "variable-declaration": "nospace" 75 | }], 76 | "use-strict": [true, 77 | "check-module" 78 | ], 79 | "variable-name": false, 80 | "whitespace": [true, 81 | "check-branch", 82 | "check-decl", 83 | "check-separator", 84 | "check-type" 85 | ] 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /typings/assertion-error/assertion-error.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for assertion-error 1.0.0 2 | // Project: https://github.com/chaijs/assertion-error 3 | // Definitions by: Bart van der Schoor 4 | // Definitions: https://github.com/borisyankov/DefinitelyTyped 5 | 6 | declare module 'assertion-error' { 7 | class AssertionError implements Error { 8 | constructor(message: string, props?: any, ssf?: Function); 9 | name: string; 10 | message: string; 11 | showDiff: boolean; 12 | stack: string; 13 | } 14 | export = AssertionError; 15 | } 16 | -------------------------------------------------------------------------------- /typings/chai/chai.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for chai 3.2.0 2 | // Project: http://chaijs.com/ 3 | // Definitions by: Jed Mao , 4 | // Bart van der Schoor , 5 | // Andrew Brown , 6 | // Olivier Chevet 7 | // Definitions: https://github.com/borisyankov/DefinitelyTyped 8 | 9 | // 10 | 11 | declare module Chai { 12 | 13 | interface ChaiStatic { 14 | expect: ExpectStatic; 15 | should(): Should; 16 | /** 17 | * Provides a way to extend the internals of Chai 18 | */ 19 | use(fn: (chai: any, utils: any) => void): any; 20 | assert: AssertStatic; 21 | config: Config; 22 | AssertionError: AssertionError; 23 | } 24 | 25 | export interface ExpectStatic extends AssertionStatic { 26 | fail(actual?: any, expected?: any, message?: string, operator?: string): void; 27 | } 28 | 29 | export interface AssertStatic extends Assert { 30 | } 31 | 32 | export interface AssertionStatic { 33 | (target: any, message?: string): Assertion; 34 | } 35 | 36 | interface ShouldAssertion { 37 | equal(value1: any, value2: any, message?: string): void; 38 | Throw: ShouldThrow; 39 | throw: ShouldThrow; 40 | exist(value: any, message?: string): void; 41 | } 42 | 43 | interface Should extends ShouldAssertion { 44 | not: ShouldAssertion; 45 | fail(actual: any, expected: any, message?: string, operator?: string): void; 46 | } 47 | 48 | interface ShouldThrow { 49 | (actual: Function): void; 50 | (actual: Function, expected: string|RegExp, message?: string): void; 51 | (actual: Function, constructor: Error|Function, expected?: string|RegExp, message?: string): void; 52 | } 53 | 54 | interface Assertion extends LanguageChains, NumericComparison, TypeComparison { 55 | not: Assertion; 56 | deep: Deep; 57 | any: KeyFilter; 58 | all: KeyFilter; 59 | a: TypeComparison; 60 | an: TypeComparison; 61 | include: Include; 62 | includes: Include; 63 | contain: Include; 64 | contains: Include; 65 | ok: Assertion; 66 | true: Assertion; 67 | false: Assertion; 68 | null: Assertion; 69 | undefined: Assertion; 70 | NaN: Assertion; 71 | exist: Assertion; 72 | empty: Assertion; 73 | arguments: Assertion; 74 | Arguments: Assertion; 75 | equal: Equal; 76 | equals: Equal; 77 | eq: Equal; 78 | eql: Equal; 79 | eqls: Equal; 80 | property: Property; 81 | ownProperty: OwnProperty; 82 | haveOwnProperty: OwnProperty; 83 | ownPropertyDescriptor: OwnPropertyDescriptor; 84 | haveOwnPropertyDescriptor: OwnPropertyDescriptor; 85 | length: Length; 86 | lengthOf: Length; 87 | match: Match; 88 | matches: Match; 89 | string(string: string, message?: string): Assertion; 90 | keys: Keys; 91 | key(string: string): Assertion; 92 | throw: Throw; 93 | throws: Throw; 94 | Throw: Throw; 95 | respondTo: RespondTo; 96 | respondsTo: RespondTo; 97 | itself: Assertion; 98 | satisfy: Satisfy; 99 | satisfies: Satisfy; 100 | closeTo(expected: number, delta: number, message?: string): Assertion; 101 | members: Members; 102 | increase: PropertyChange; 103 | increases: PropertyChange; 104 | decrease: PropertyChange; 105 | decreases: PropertyChange; 106 | change: PropertyChange; 107 | changes: PropertyChange; 108 | extensible: Assertion; 109 | sealed: Assertion; 110 | frozen: Assertion; 111 | 112 | } 113 | 114 | interface LanguageChains { 115 | to: Assertion; 116 | be: Assertion; 117 | been: Assertion; 118 | is: Assertion; 119 | that: Assertion; 120 | which: Assertion; 121 | and: Assertion; 122 | has: Assertion; 123 | have: Assertion; 124 | with: Assertion; 125 | at: Assertion; 126 | of: Assertion; 127 | same: Assertion; 128 | } 129 | 130 | interface NumericComparison { 131 | above: NumberComparer; 132 | gt: NumberComparer; 133 | greaterThan: NumberComparer; 134 | least: NumberComparer; 135 | gte: NumberComparer; 136 | below: NumberComparer; 137 | lt: NumberComparer; 138 | lessThan: NumberComparer; 139 | most: NumberComparer; 140 | lte: NumberComparer; 141 | within(start: number, finish: number, message?: string): Assertion; 142 | } 143 | 144 | interface NumberComparer { 145 | (value: number, message?: string): Assertion; 146 | } 147 | 148 | interface TypeComparison { 149 | (type: string, message?: string): Assertion; 150 | instanceof: InstanceOf; 151 | instanceOf: InstanceOf; 152 | } 153 | 154 | interface InstanceOf { 155 | (constructor: Object, message?: string): Assertion; 156 | } 157 | 158 | interface Deep { 159 | equal: Equal; 160 | include: Include; 161 | property: Property; 162 | members: Members; 163 | } 164 | 165 | interface KeyFilter { 166 | keys: Keys; 167 | } 168 | 169 | interface Equal { 170 | (value: any, message?: string): Assertion; 171 | } 172 | 173 | interface Property { 174 | (name: string, value?: any, message?: string): Assertion; 175 | } 176 | 177 | interface OwnProperty { 178 | (name: string, message?: string): Assertion; 179 | } 180 | 181 | interface OwnPropertyDescriptor { 182 | (name: string, descriptor: PropertyDescriptor, message?: string): Assertion; 183 | (name: string, message?: string): Assertion; 184 | } 185 | 186 | interface Length extends LanguageChains, NumericComparison { 187 | (length: number, message?: string): Assertion; 188 | } 189 | 190 | interface Include { 191 | (value: Object, message?: string): Assertion; 192 | (value: string, message?: string): Assertion; 193 | (value: number, message?: string): Assertion; 194 | keys: Keys; 195 | members: Members; 196 | any: KeyFilter; 197 | all: KeyFilter; 198 | } 199 | 200 | interface Match { 201 | (regexp: RegExp|string, message?: string): Assertion; 202 | } 203 | 204 | interface Keys { 205 | (...keys: string[]): Assertion; 206 | (keys: any[]): Assertion; 207 | (keys: Object): Assertion; 208 | } 209 | 210 | interface Throw { 211 | (): Assertion; 212 | (expected: string, message?: string): Assertion; 213 | (expected: RegExp, message?: string): Assertion; 214 | (constructor: Error, expected?: string, message?: string): Assertion; 215 | (constructor: Error, expected?: RegExp, message?: string): Assertion; 216 | (constructor: Function, expected?: string, message?: string): Assertion; 217 | (constructor: Function, expected?: RegExp, message?: string): Assertion; 218 | } 219 | 220 | interface RespondTo { 221 | (method: string, message?: string): Assertion; 222 | } 223 | 224 | interface Satisfy { 225 | (matcher: Function, message?: string): Assertion; 226 | } 227 | 228 | interface Members { 229 | (set: any[], message?: string): Assertion; 230 | } 231 | 232 | interface PropertyChange { 233 | (object: Object, prop: string, msg?: string): Assertion; 234 | } 235 | 236 | export interface Assert { 237 | /** 238 | * @param expression Expression to test for truthiness. 239 | * @param message Message to display on error. 240 | */ 241 | (expression: any, message?: string): void; 242 | 243 | fail(actual?: any, expected?: any, msg?: string, operator?: string): void; 244 | 245 | ok(val: any, msg?: string): void; 246 | isOk(val: any, msg?: string): void; 247 | notOk(val: any, msg?: string): void; 248 | isNotOk(val: any, msg?: string): void; 249 | 250 | equal(act: any, exp: any, msg?: string): void; 251 | notEqual(act: any, exp: any, msg?: string): void; 252 | 253 | strictEqual(act: any, exp: any, msg?: string): void; 254 | notStrictEqual(act: any, exp: any, msg?: string): void; 255 | 256 | deepEqual(act: any, exp: any, msg?: string): void; 257 | notDeepEqual(act: any, exp: any, msg?: string): void; 258 | 259 | isTrue(val: any, msg?: string): void; 260 | isFalse(val: any, msg?: string): void; 261 | 262 | isNull(val: any, msg?: string): void; 263 | isNotNull(val: any, msg?: string): void; 264 | 265 | isUndefined(val: any, msg?: string): void; 266 | isDefined(val: any, msg?: string): void; 267 | 268 | isNaN(val: any, msg?: string): void; 269 | isNotNaN(val: any, msg?: string): void; 270 | 271 | isAbove(val: number, abv: number, msg?: string): void; 272 | isBelow(val: number, blw: number, msg?: string): void; 273 | 274 | isFunction(val: any, msg?: string): void; 275 | isNotFunction(val: any, msg?: string): void; 276 | 277 | isObject(val: any, msg?: string): void; 278 | isNotObject(val: any, msg?: string): void; 279 | 280 | isArray(val: any, msg?: string): void; 281 | isNotArray(val: any, msg?: string): void; 282 | 283 | isString(val: any, msg?: string): void; 284 | isNotString(val: any, msg?: string): void; 285 | 286 | isNumber(val: any, msg?: string): void; 287 | isNotNumber(val: any, msg?: string): void; 288 | 289 | isBoolean(val: any, msg?: string): void; 290 | isNotBoolean(val: any, msg?: string): void; 291 | 292 | typeOf(val: any, type: string, msg?: string): void; 293 | notTypeOf(val: any, type: string, msg?: string): void; 294 | 295 | instanceOf(val: any, type: Function, msg?: string): void; 296 | notInstanceOf(val: any, type: Function, msg?: string): void; 297 | 298 | include(exp: string, inc: any, msg?: string): void; 299 | include(exp: any[], inc: any, msg?: string): void; 300 | 301 | notInclude(exp: string, inc: any, msg?: string): void; 302 | notInclude(exp: any[], inc: any, msg?: string): void; 303 | 304 | match(exp: any, re: RegExp, msg?: string): void; 305 | notMatch(exp: any, re: RegExp, msg?: string): void; 306 | 307 | property(obj: Object, prop: string, msg?: string): void; 308 | notProperty(obj: Object, prop: string, msg?: string): void; 309 | deepProperty(obj: Object, prop: string, msg?: string): void; 310 | notDeepProperty(obj: Object, prop: string, msg?: string): void; 311 | 312 | propertyVal(obj: Object, prop: string, val: any, msg?: string): void; 313 | propertyNotVal(obj: Object, prop: string, val: any, msg?: string): void; 314 | 315 | deepPropertyVal(obj: Object, prop: string, val: any, msg?: string): void; 316 | deepPropertyNotVal(obj: Object, prop: string, val: any, msg?: string): void; 317 | 318 | lengthOf(exp: any, len: number, msg?: string): void; 319 | //alias frenzy 320 | throw(fn: Function, msg?: string): void; 321 | throw(fn: Function, regExp: RegExp): void; 322 | throw(fn: Function, errType: Function, msg?: string): void; 323 | throw(fn: Function, errType: Function, regExp: RegExp): void; 324 | 325 | throws(fn: Function, msg?: string): void; 326 | throws(fn: Function, regExp: RegExp): void; 327 | throws(fn: Function, errType: Function, msg?: string): void; 328 | throws(fn: Function, errType: Function, regExp: RegExp): void; 329 | 330 | Throw(fn: Function, msg?: string): void; 331 | Throw(fn: Function, regExp: RegExp): void; 332 | Throw(fn: Function, errType: Function, msg?: string): void; 333 | Throw(fn: Function, errType: Function, regExp: RegExp): void; 334 | 335 | doesNotThrow(fn: Function, msg?: string): void; 336 | doesNotThrow(fn: Function, regExp: RegExp): void; 337 | doesNotThrow(fn: Function, errType: Function, msg?: string): void; 338 | doesNotThrow(fn: Function, errType: Function, regExp: RegExp): void; 339 | 340 | operator(val: any, operator: string, val2: any, msg?: string): void; 341 | closeTo(act: number, exp: number, delta: number, msg?: string): void; 342 | 343 | sameMembers(set1: any[], set2: any[], msg?: string): void; 344 | sameDeepMembers(set1: any[], set2: any[], msg?: string): void; 345 | includeMembers(superset: any[], subset: any[], msg?: string): void; 346 | 347 | ifError(val: any, msg?: string): void; 348 | 349 | isExtensible(obj: {}, msg?: string): void; 350 | extensible(obj: {}, msg?: string): void; 351 | isNotExtensible(obj: {}, msg?: string): void; 352 | notExtensible(obj: {}, msg?: string): void; 353 | 354 | isSealed(obj: {}, msg?: string): void; 355 | sealed(obj: {}, msg?: string): void; 356 | isNotSealed(obj: {}, msg?: string): void; 357 | notSealed(obj: {}, msg?: string): void; 358 | 359 | isFrozen(obj: Object, msg?: string): void; 360 | frozen(obj: Object, msg?: string): void; 361 | isNotFrozen(obj: Object, msg?: string): void; 362 | notFrozen(obj: Object, msg?: string): void; 363 | 364 | 365 | } 366 | 367 | export interface Config { 368 | includeStack: boolean; 369 | } 370 | 371 | export class AssertionError { 372 | constructor(message: string, _props?: any, ssf?: Function); 373 | name: string; 374 | message: string; 375 | showDiff: boolean; 376 | stack: string; 377 | } 378 | } 379 | 380 | declare var chai: Chai.ChaiStatic; 381 | 382 | declare module "chai" { 383 | export = chai; 384 | } 385 | 386 | interface Object { 387 | should: Chai.Assertion; 388 | } 389 | -------------------------------------------------------------------------------- /typings/mocha/mocha.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for mocha 2.2.5 2 | // Project: http://mochajs.org/ 3 | // Definitions by: Kazi Manzur Rashid , otiai10 , jt000 , Vadim Macagon 4 | // Definitions: https://github.com/borisyankov/DefinitelyTyped 5 | 6 | interface MochaSetupOptions { 7 | //milliseconds to wait before considering a test slow 8 | slow?: number; 9 | 10 | // timeout in milliseconds 11 | timeout?: number; 12 | 13 | // ui name "bdd", "tdd", "exports" etc 14 | ui?: string; 15 | 16 | //array of accepted globals 17 | globals?: any[]; 18 | 19 | // reporter instance (function or string), defaults to `mocha.reporters.Spec` 20 | reporter?: any; 21 | 22 | // bail on the first test failure 23 | bail?: boolean; 24 | 25 | // ignore global leaks 26 | ignoreLeaks?: boolean; 27 | 28 | // grep string or regexp to filter tests with 29 | grep?: any; 30 | } 31 | 32 | interface MochaDone { 33 | (error?: Error): void; 34 | } 35 | 36 | declare var mocha: Mocha; 37 | declare var describe: Mocha.IContextDefinition; 38 | declare var xdescribe: Mocha.IContextDefinition; 39 | // alias for `describe` 40 | declare var context: Mocha.IContextDefinition; 41 | // alias for `describe` 42 | declare var suite: Mocha.IContextDefinition; 43 | declare var it: Mocha.ITestDefinition; 44 | declare var xit: Mocha.ITestDefinition; 45 | // alias for `it` 46 | declare var test: Mocha.ITestDefinition; 47 | 48 | declare function before(action: () => void): void; 49 | 50 | declare function before(action: (done: MochaDone) => void): void; 51 | 52 | declare function before(description: string, action: () => void): void; 53 | 54 | declare function before(description: string, action: (done: MochaDone) => void): void; 55 | 56 | declare function setup(action: () => void): void; 57 | 58 | declare function setup(action: (done: MochaDone) => void): void; 59 | 60 | declare function after(action: () => void): void; 61 | 62 | declare function after(action: (done: MochaDone) => void): void; 63 | 64 | declare function after(description: string, action: () => void): void; 65 | 66 | declare function after(description: string, action: (done: MochaDone) => void): void; 67 | 68 | declare function teardown(action: () => void): void; 69 | 70 | declare function teardown(action: (done: MochaDone) => void): void; 71 | 72 | declare function beforeEach(action: () => void): void; 73 | 74 | declare function beforeEach(action: (done: MochaDone) => void): void; 75 | 76 | declare function beforeEach(description: string, action: () => void): void; 77 | 78 | declare function beforeEach(description: string, action: (done: MochaDone) => void): void; 79 | 80 | declare function suiteSetup(action: () => void): void; 81 | 82 | declare function suiteSetup(action: (done: MochaDone) => void): void; 83 | 84 | declare function afterEach(action: () => void): void; 85 | 86 | declare function afterEach(action: (done: MochaDone) => void): void; 87 | 88 | declare function afterEach(description: string, action: () => void): void; 89 | 90 | declare function afterEach(description: string, action: (done: MochaDone) => void): void; 91 | 92 | declare function suiteTeardown(action: () => void): void; 93 | 94 | declare function suiteTeardown(action: (done: MochaDone) => void): void; 95 | 96 | declare class Mocha { 97 | constructor(options?: { 98 | grep?: RegExp; 99 | ui?: string; 100 | reporter?: string; 101 | timeout?: number; 102 | bail?: boolean; 103 | }); 104 | 105 | /** Setup mocha with the given options. */ 106 | setup(options: MochaSetupOptions): Mocha; 107 | bail(value?: boolean): Mocha; 108 | addFile(file: string): Mocha; 109 | /** Sets reporter by name, defaults to "spec". */ 110 | reporter(name: string): Mocha; 111 | /** Sets reporter constructor, defaults to mocha.reporters.Spec. */ 112 | reporter(reporter: (runner: Mocha.IRunner, options: any) => any): Mocha; 113 | ui(value: string): Mocha; 114 | grep(value: string): Mocha; 115 | grep(value: RegExp): Mocha; 116 | invert(): Mocha; 117 | ignoreLeaks(value: boolean): Mocha; 118 | checkLeaks(): Mocha; 119 | /** 120 | * Function to allow assertion libraries to throw errors directly into mocha. 121 | * This is useful when running tests in a browser because window.onerror will 122 | * only receive the 'message' attribute of the Error. 123 | */ 124 | throwError(error: Error): void; 125 | /** Enables growl support. */ 126 | growl(): Mocha; 127 | globals(value: string): Mocha; 128 | globals(values: string[]): Mocha; 129 | useColors(value: boolean): Mocha; 130 | useInlineDiffs(value: boolean): Mocha; 131 | timeout(value: number): Mocha; 132 | slow(value: number): Mocha; 133 | enableTimeouts(value: boolean): Mocha; 134 | asyncOnly(value: boolean): Mocha; 135 | noHighlighting(value: boolean): Mocha; 136 | /** Runs tests and invokes `onComplete()` when finished. */ 137 | run(onComplete?: (failures: number) => void): Mocha.IRunner; 138 | } 139 | 140 | // merge the Mocha class declaration with a module 141 | declare module Mocha { 142 | /** Partial interface for Mocha's `Runnable` class. */ 143 | interface IRunnable { 144 | title: string; 145 | fn: Function; 146 | async: boolean; 147 | sync: boolean; 148 | timedOut: boolean; 149 | } 150 | 151 | /** Partial interface for Mocha's `Suite` class. */ 152 | interface ISuite { 153 | parent: ISuite; 154 | title: string; 155 | 156 | fullTitle(): string; 157 | } 158 | 159 | /** Partial interface for Mocha's `Test` class. */ 160 | interface ITest extends IRunnable { 161 | parent: ISuite; 162 | pending: boolean; 163 | 164 | fullTitle(): string; 165 | } 166 | 167 | /** Partial interface for Mocha's `Runner` class. */ 168 | interface IRunner {} 169 | 170 | interface IContextDefinition { 171 | (description: string, spec: () => void): ISuite; 172 | only(description: string, spec: () => void): ISuite; 173 | skip(description: string, spec: () => void): void; 174 | timeout(ms: number): void; 175 | } 176 | 177 | interface ITestDefinition { 178 | (expectation: string, assertion?: () => void): ITest; 179 | (expectation: string, assertion?: (done: MochaDone) => void): ITest; 180 | only(expectation: string, assertion?: () => void): ITest; 181 | only(expectation: string, assertion?: (done: MochaDone) => void): ITest; 182 | skip(expectation: string, assertion?: () => void): void; 183 | skip(expectation: string, assertion?: (done: MochaDone) => void): void; 184 | timeout(ms: number): void; 185 | } 186 | 187 | export module reporters { 188 | export class Base { 189 | stats: { 190 | suites: number; 191 | tests: number; 192 | passes: number; 193 | pending: number; 194 | failures: number; 195 | }; 196 | 197 | constructor(runner: IRunner); 198 | } 199 | 200 | export class Doc extends Base {} 201 | export class Dot extends Base {} 202 | export class HTML extends Base {} 203 | export class HTMLCov extends Base {} 204 | export class JSON extends Base {} 205 | export class JSONCov extends Base {} 206 | export class JSONStream extends Base {} 207 | export class Landing extends Base {} 208 | export class List extends Base {} 209 | export class Markdown extends Base {} 210 | export class Min extends Base {} 211 | export class Nyan extends Base {} 212 | export class Progress extends Base { 213 | /** 214 | * @param options.open String used to indicate the start of the progress bar. 215 | * @param options.complete String used to indicate a complete test on the progress bar. 216 | * @param options.incomplete String used to indicate an incomplete test on the progress bar. 217 | * @param options.close String used to indicate the end of the progress bar. 218 | */ 219 | constructor(runner: IRunner, options?: { 220 | open?: string; 221 | complete?: string; 222 | incomplete?: string; 223 | close?: string; 224 | }); 225 | } 226 | export class Spec extends Base {} 227 | export class TAP extends Base {} 228 | export class XUnit extends Base { 229 | constructor(runner: IRunner, options?: any); 230 | } 231 | } 232 | } 233 | 234 | declare module "mocha" { 235 | export = Mocha; 236 | } 237 | -------------------------------------------------------------------------------- /typings/tsd.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | /// 5 | --------------------------------------------------------------------------------