UNPKG

@iotile/iotile-device

Version:

A typescript library for interfacing with IOTile BLE devices

397 lines 16.5 kB
"use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); Object.defineProperty(exports, "__esModule", { value: true }); ///<reference path="../../typings/cordova_plugins.d.ts"/> var iotile_common_1 = require("@iotile/iotile-common"); var report_reassembler_1 = require("./report-reassembler"); var config_1 = require("../config"); var RawReading = /** @class */ (function () { function RawReading(stream, value, timestamp, timebase, id) { this._stream = stream; this._value = value; this._raw_timestamp = timestamp; this._time = new Date(timebase.valueOf() + timestamp * 1000); if (id !== undefined) { this._id = id; } else { this._id = 0; } } Object.defineProperty(RawReading.prototype, "timestamp", { get: function () { return this._raw_timestamp; }, enumerable: true, configurable: true }); Object.defineProperty(RawReading.prototype, "value", { get: function () { return this._value; }, enumerable: true, configurable: true }); Object.defineProperty(RawReading.prototype, "stream", { get: function () { return this._stream; }, enumerable: true, configurable: true }); Object.defineProperty(RawReading.prototype, "id", { get: function () { return this._id; }, enumerable: true, configurable: true }); Object.defineProperty(RawReading.prototype, "time", { get: function () { return this._time; }, enumerable: true, configurable: true }); Object.defineProperty(RawReading.prototype, "variable", { get: function () { return iotile_common_1.numberToHexString(this.stream, 4); }, enumerable: true, configurable: true }); return RawReading; }()); exports.RawReading = RawReading; var IOTileReport = /** @class */ (function () { function IOTileReport() { } return IOTileReport; }()); exports.IOTileReport = IOTileReport; var IndividualReport = /** @class */ (function (_super) { __extends(IndividualReport, _super); function IndividualReport(uuid, sentTime, reading) { var _this = _super.call(this) || this; _this._uuid = uuid; _this._reading = reading; _this._sentTimestamp = sentTime; return _this; } /** * Update the IndividualReport using formatting data from the cloud to be able to show * accurate realtime data about the device stream. * * @param fmt: a raw_value_format code from the cloud that indicates what format * the binary device data should be interpreted as. */ IndividualReport.prototype.decodeUsingFormat = function (fmt) { if (fmt == '<l') { var signedValue = void 0; var rawData = iotile_common_1.packArrayBuffer('L', this._reading.value); signedValue = iotile_common_1.unpackArrayBuffer('l', rawData)[0]; this._reading = new RawReading(this._reading.stream, signedValue, this._reading.timestamp, this._reading.time, this._reading.id); } }; Object.defineProperty(IndividualReport.prototype, "deviceID", { get: function () { return this._uuid; }, enumerable: true, configurable: true }); Object.defineProperty(IndividualReport.prototype, "reading", { get: function () { return this._reading; }, enumerable: true, configurable: true }); Object.defineProperty(IndividualReport.prototype, "sentTimestamp", { get: function () { return this._sentTimestamp; }, enumerable: true, configurable: true }); return IndividualReport; }(IOTileReport)); exports.IndividualReport = IndividualReport; var REPORT_HEADER_SIZE = 20; exports.USER_REPORT_STREAMER = 0; exports.SYSTEM_REPORT_STREAMER = 1; exports.COMBINED_REPORT_STREAMER = 0xFF; var SignedReportSelectors; (function (SignedReportSelectors) { SignedReportSelectors[SignedReportSelectors["UserOutputs"] = 22527] = "UserOutputs"; SignedReportSelectors[SignedReportSelectors["SystemOutputs"] = 24575] = "SystemOutputs"; SignedReportSelectors[SignedReportSelectors["CombinedOutputs"] = 55295] = "CombinedOutputs"; })(SignedReportSelectors = exports.SignedReportSelectors || (exports.SignedReportSelectors = {})); var SignatureFlags; (function (SignatureFlags) { SignatureFlags[SignatureFlags["HashOnly"] = 0] = "HashOnly"; SignatureFlags[SignatureFlags["SignedUserKey"] = 1] = "SignedUserKey"; SignatureFlags[SignatureFlags["SignedDeviceKey"] = 2] = "SignedDeviceKey"; })(SignatureFlags = exports.SignatureFlags || (exports.SignatureFlags = {})); var SignatureStatus; (function (SignatureStatus) { SignatureStatus[SignatureStatus["Valid"] = 0] = "Valid"; SignatureStatus[SignatureStatus["Invalid"] = 1] = "Invalid"; SignatureStatus[SignatureStatus["Unknown"] = 2] = "Unknown"; })(SignatureStatus = exports.SignatureStatus || (exports.SignatureStatus = {})); var StreamMatchOperator; (function (StreamMatchOperator) { StreamMatchOperator[StreamMatchOperator["UserOnly"] = 0] = "UserOnly"; StreamMatchOperator[StreamMatchOperator["SystemOnly"] = 1] = "SystemOnly"; StreamMatchOperator[StreamMatchOperator["UserAndBreaks"] = 2] = "UserAndBreaks"; StreamMatchOperator[StreamMatchOperator["UserAndSystem"] = 3] = "UserAndSystem"; })(StreamMatchOperator = exports.StreamMatchOperator || (exports.StreamMatchOperator = {})); var StreamType; (function (StreamType) { StreamType[StreamType["Storage"] = 0] = "Storage"; StreamType[StreamType["Unbuffered"] = 1] = "Unbuffered"; StreamType[StreamType["Constant"] = 2] = "Constant"; StreamType[StreamType["Input"] = 3] = "Input"; StreamType[StreamType["Count"] = 4] = "Count"; StreamType[StreamType["Output"] = 5] = "Output"; StreamType[StreamType["Realtime"] = 6] = "Realtime"; })(StreamType = exports.StreamType || (exports.StreamType = {})); var StreamSelector = /** @class */ (function () { function StreamSelector(encodedSelector) { var _a; _a = StreamSelector.decode(encodedSelector), this.type = _a[0], this.code = _a[1], this.match_op = _a[2]; this.isWildcard = (this.code === StreamSelector.WILDCARD); } StreamSelector.prototype.matches = function (streamID) { var _a = StreamSelector.decode(streamID), type = _a[0], code = _a[1], op = _a[2]; //Stream IDs must be either system or user, not any of the combined selectors if (op === StreamMatchOperator.UserAndSystem || op === StreamMatchOperator.UserAndBreaks) return false; if (!this.isWildcard && this.code !== code) return false; if (this.type !== type) return false; if (this.match_op === StreamMatchOperator.SystemOnly && op == StreamMatchOperator.UserOnly) return false; if (this.match_op === StreamMatchOperator.UserOnly && op == StreamMatchOperator.SystemOnly) return false; /* * The UserAndBreaks selector matches all user streams + the global reboot stream but no other system streams. */ if (this.match_op === StreamMatchOperator.UserAndBreaks && op == StreamMatchOperator.SystemOnly && streamID !== StreamSelector.REBOOT_STREAM) return false; return true; }; StreamSelector.decode = function (encodedSelector) { if (encodedSelector < 0 || encodedSelector > 0xFFFF) throw new iotile_common_1.ArgumentError("invalid number in StreamSelector that is not in [0, 65535]: " + encodedSelector); if (encodedSelector !== Math.floor(encodedSelector)) throw new iotile_common_1.ArgumentError("You must create a StreamSelector with a whole number: " + encodedSelector); var isSystem = !!(encodedSelector & (1 << 11)); var includeBreaks = !!(encodedSelector & (1 << 15)); var match_op = StreamSelector.getOperator(isSystem, includeBreaks); var code = encodedSelector & ((1 << 11) - 1); var type = (encodedSelector >> 12) & 7; return [type, code, match_op]; }; StreamSelector.getOperator = function (isSystem, includeBreaks) { if (isSystem && !includeBreaks) return StreamMatchOperator.SystemOnly; if (!isSystem && !includeBreaks) return StreamMatchOperator.UserOnly; if (isSystem && includeBreaks) return StreamMatchOperator.UserAndSystem; //!isSystem && includeBreaks return StreamMatchOperator.UserAndBreaks; }; StreamSelector.WILDCARD = (1 << 11) - 1; StreamSelector.REBOOT_STREAM = 0x5C00; return StreamSelector; }()); exports.StreamSelector = StreamSelector; var SignedListReport = /** @class */ (function (_super) { __extends(SignedListReport, _super); function SignedListReport(uuid, streamer, rawData, receivedTime) { var _this = _super.call(this) || this; _this._highestID = 0; _this._lowestID = 0; _this._uuid = uuid; _this._readings = []; _this._rawData = rawData; _this._streamer = streamer; _this._receivedTime = receivedTime; _this._header = SignedListReport.extractHeader(rawData); _this._valid = _this.validateSignature(); if (_this._valid === SignatureStatus.Valid) { _this.updateReadings(_this._rawData); _this.updateReadingRange(_this._readings); } return _this; } SignedListReport.extractHeader = function (data) { if (data.byteLength < REPORT_HEADER_SIZE) { throw new iotile_common_1.ArgumentError("Invalid report that was not long enough to contain a valid header"); } var header = iotile_common_1.unpackArrayBuffer('BBHLLLBBH', data.slice(0, REPORT_HEADER_SIZE)); return { format: header[0], lengthLow: header[1], lengthHigh: header[2], uuid: header[3], reportID: header[4], sentTime: header[5], signatureFlags: header[6], streamer: header[7], selector: header[8], decodedSelector: new StreamSelector(header[8]) }; }; Object.defineProperty(SignedListReport.prototype, "deviceID", { get: function () { return this._uuid; }, enumerable: true, configurable: true }); Object.defineProperty(SignedListReport.prototype, "readings", { get: function () { return this._readings; }, enumerable: true, configurable: true }); Object.defineProperty(SignedListReport.prototype, "validity", { get: function () { return this._valid; }, enumerable: true, configurable: true }); Object.defineProperty(SignedListReport.prototype, "rawData", { get: function () { return this._rawData; }, enumerable: true, configurable: true }); Object.defineProperty(SignedListReport.prototype, "streamer", { get: function () { return this._streamer; }, enumerable: true, configurable: true }); Object.defineProperty(SignedListReport.prototype, "receivedTime", { get: function () { return this._receivedTime; }, enumerable: true, configurable: true }); Object.defineProperty(SignedListReport.prototype, "readingIDRange", { get: function () { return [this._lowestID, this._highestID]; }, enumerable: true, configurable: true }); Object.defineProperty(SignedListReport.prototype, "header", { get: function () { return this._header; }, enumerable: true, configurable: true }); SignedListReport.prototype.updateReadingRange = function (readings) { //Calculate lowest and highest ids based on decoded readings if (readings.length == 0) { this._lowestID = 0; this._highestID = 0; } else { //Initialize with the first reading to avoid needing to use sentinal values this._lowestID = readings[0].id; this._highestID = readings[0].id; for (var i = 1; i < readings.length; ++i) { var id = readings[i].id; if (id < this._lowestID) { this._lowestID = id; } if (id > this._highestID) { this._highestID = id; } } } }; SignedListReport.prototype.updateReadings = function (rawData) { var readings = []; var onTime = new Date(this._receivedTime.valueOf() - (this._header.sentTime * 1000)); //Now decode and parse the actual readings in the report // get length of readings var allReadingsData = rawData.slice(20, rawData.byteLength - 24); for (var i = 0; i < allReadingsData.byteLength; i += 16) { var readingData = allReadingsData.slice(i, i + 16); var reading = iotile_common_1.unpackArrayBuffer("HHLLL", readingData); var stream = reading[0]; //reading[1] is reserved var readingID = reading[2]; var readingTimestamp = reading[3]; var readingValue = reading[4]; var parsedReading = new RawReading(stream, readingValue, readingTimestamp, onTime, readingID); readings.push(parsedReading); } this._readings = readings; this.updateReadingRange(readings); }; /** * Note that this method is designed to be called from the constructor * only. It needs to be followed by calls to updateReadings() and updateReadingRange() * since it may modify the raw report data. */ SignedListReport.prototype.validateSignature = function () { var calc = new iotile_common_1.SHA256Calculator(); if (this._header.signatureFlags != SignatureFlags.HashOnly) { return SignatureStatus.Unknown; } var signedData = this._rawData.slice(0, this._rawData.byteLength - 16); var embeddedSig = this._rawData.slice(this._rawData.byteLength - 16); var signature = calc.calculateSignature(signedData); if (calc.compareSignatures(embeddedSig, signature)) { return SignatureStatus.Valid; } /** * If the signature is invalid, attempt to automatically fix it assuming that the * cause is due to the Android bluetooth stack sending us out-of-order report chunks. */ config_1.catReports.warn("Report signature invalid, attempting to reassemble"); var reassembler = new report_reassembler_1.ReportReassembler(this.rawData); if (reassembler.fixOutOfOrderChunks()) { config_1.catReports.info("Report successfully fixed"); var errors = reassembler.getTranspositions(); var errorStrings = []; for (var _i = 0, errors_1 = errors; _i < errors_1.length; _i++) { var error = errors_1[_i]; var errorString_1 = error.src + " -> " + error.dst; errorStrings.push(errorString_1); } var errorString = errorStrings.join(", "); config_1.catReports.info("Report transpositions were: " + errorString); var newReport = reassembler.getFixedReport(); this._rawData = newReport; return SignatureStatus.Valid; } config_1.catReports.error("Unable to correct report signature", null); return SignatureStatus.Invalid; }; return SignedListReport; }(IOTileReport)); exports.SignedListReport = SignedListReport; //# sourceMappingURL=iotile-reports.js.map