UNPKG

@iotile/iotile-device

Version:

A typescript library for interfacing with IOTile BLE devices

216 lines 9.35 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var iotile_common_1 = require("@iotile/iotile-common"); var config_1 = require("../config"); var SECONDS_AT_2000 = Date.UTC(2000, 0, 1).valueOf() / 1000; /** * This class assigns UTC dates to readings based on their sequential reading ID and local time. * * Internally it works based on the following principle. It knows that the sequential reading ID * is monotonically increasing but the local time can reset to 0 every time there is a timeBreak * in the data (e.g. the device reboots). So it constructs a series of TimeSegments which are * ranges of reading IDs where there are not breaks. This means that each break ends the previous * segment and starts the next segment. * * Inside each segment there needs to be at least one anchor point where the UTC time and local time * are both known. Once there is an anchor point in a given segment, all other readings in that * segment can be assigned UTC times by looking at their local time offset from the anchor point. * * If allowImprecise is passed, this class should still try to assign a UTC time to a reading even * if it occurs in a segment with no anchor points by finding the first subsequent segment that does * have an anchor point and back-calculating from there assuming that all breaks between segments * were infinitely short. */ var UTCAssigner = /** @class */ (function () { function UTCAssigner(options) { this.imprecise = options.allowImprecise; this.logger = config_1.catUTCAssigner; this.anchorPoints = []; this.addedIDSet = {}; this.anchorPointsSorted = false; this.anchorStreams = {}; this.breakStreams = {}; this.initBreakStreams(); } UTCAssigner.prototype.initBreakStreams = function () { this.breakStreams[0x5c00] = true; }; /** * Assign a UTC date to a reading based on all previously added anchor and break * points. If a UTC time cannot be assigned because there is insufficient data * or because one of the options passed to the constructor does not allow it, * throw an ArgumentError. */ UTCAssigner.prototype.assignUTCTimestamp = function (readingID, uptime) { /** * uptimes embedded in the accelerometer tile are stored as placeholders with the * fixed value 0xFFFFFFFF, which should be interpreted as "I don't know the uptime" */ if (uptime === 0xFFFFFFFF) uptime = null; if (this.anchorPoints.length === 0) throw new iotile_common_1.ArgumentError("Cannot assign timestamp because there are no anchor points"); if (readingID > this.anchorPoints[this.anchorPoints.length - 1].readingId) throw new iotile_common_1.ArgumentError("Extrapolation of UTC times is not yet supported"); var i = this.bisectLeftAnchors(readingID); var last = copyAnchor(this.anchorPoints[i]); if (uptime != null) last.uptime = uptime; var exact = true; var accumDelta = 0; if (last.readingId === readingID && last.utcTime != null) return last.utcTime; i += 1; while (i < this.anchorPoints.length) { var curr = this.anchorPoints[i]; if (last.uptime == null || curr.uptime == null) { exact = false; } else if (curr.isBreak || curr.uptime < last.uptime) { exact = false; } else { accumDelta += curr.uptime - last.uptime; } last = curr; if (curr.utcTime != null) break; i += 1; } if (last.utcTime == null) throw new iotile_common_1.ArgumentError("There were no points with a UTC reference after the designed reading id"); if (this.imprecise === false && !exact) throw new iotile_common_1.ArgumentError("Could not assign precise UTC Timestamp"); return new Date(last.utcTime.valueOf() - (accumDelta * 1000)); }; UTCAssigner.prototype.bisectLeftAnchors = function (readingID) { var low = 0; var high = this.anchorPoints.length; this.ensureAnchorPointsSorted(); while (low < high) { var mid = low + high >>> 1; if (this.anchorPoints[mid].readingId < readingID) { low = mid + 1; } else { high = mid; } } return low; }; UTCAssigner.prototype.ensureAnchorPointsSorted = function () { if (this.anchorPointsSorted) return; this.anchorPoints.sort(function (a, b) { return a.readingId - b.readingId; }); this.anchorPointsSorted = true; }; /* * Explicitly inform the UTCAssigner that we know a specific * correspondence between a local time (the device's uptime) and * utc time. It is these anchor points that are used as ground * truth values to assign utc times to all other points. */ UTCAssigner.prototype.addAnchorPoint = function (readingID, uptime, utc, isBreak) { if (isBreak === void 0) { isBreak = false; } if (readingID === 0) return; if (uptime == null && utc == null) return; if (readingID in this.addedIDSet) return; /** * If the uptime itself is specified as a UTC time rather than an uptime, * convert it to a UTC time and null out the uptime since we don't know * what the uptime was (since the timestamp was in UTC rather than uptime). * * If an explicit UTC time was also passed, ignore this anchor point and return * since we would know which utc to trust, the uptime interpreted as UTC or the * one that was explicitly passed. */ if (uptime != null && uptime & (1 << 31)) { if (utc != null) return; //Mask out high bit to get the actual seconds since 2000 uptime &= (1 << 31) - 1; utc = rtcTimestampToDate(uptime); uptime = null; } var anchorPoint = { readingId: readingID, uptime: uptime, utcTime: utc, isBreak: isBreak }; this.anchorPoints.push(anchorPoint); this.addedIDSet[readingID] = true; this.anchorPointsSorted = false; }; UTCAssigner.prototype.addReading = function (reading) { var isBreak = false; var utc = null; if (reading.stream in this.breakStreams) isBreak = true; if (reading.stream in this.anchorStreams) { utc = this.anchorStreams[reading.stream](reading.stream, reading.id, reading.timestamp, reading.value); } this.addAnchorPoint(reading.id, reading.timestamp, utc, isBreak); }; /** * Inform the UTCAssigner that whenever it sees a value in the given * stream, it can infer that its value is explicitly a UTC timestamp. * This allows the UTCAssigner to automatically call addAnchorPoint * when it processes a streamer report that contains streams that could * act as anchors. * * If the value of the stream cannot be directly interpreted as the number * of seconds since the year 2000, you can pass an optional callable that * will be called to determine the correct UTC timestamp. * * You can pass a literal string "rtc" or "epoch" for valueProcessor if you want * the value to be treated as seconds since 1/1/2000 or seconds since 1/1/1970 respectively. * * Alternatively, you can pass a function that returns a Date. */ UTCAssigner.prototype.markAnchorStream = function (streamID, valueProcessor) { if (valueProcessor == null || valueProcessor === "rtc") { valueProcessor = function (_streamID, _readingID, _uptime, value) { // assume value can be interpreted as-is as seconds since 2000 return rtcTimestampToDate(value); }; } else if (valueProcessor === "epoch") { valueProcessor = function (_streamID, _readingID, _uptime, value) { // assume value can be interpreted as-is as seconds since 2000 return new Date(value * 1000); }; } this.anchorStreams[streamID] = valueProcessor; }; /** * Automatically call addAnchorPoint for all values found in * a stream previously passed to markAnchorStream. */ UTCAssigner.prototype.addAnchorsFromReport = function (report) { for (var _i = 0, _a = report.readings; _i < _a.length; _i++) { var reading = _a[_i]; this.addReading(reading); } this.addAnchorPoint(report.header.reportID, report.header.sentTime, report.receivedTime); }; return UTCAssigner; }()); exports.UTCAssigner = UTCAssigner; function rtcTimestampToDate(seconds) { return new Date((seconds + SECONDS_AT_2000) * 1000); } function copyAnchor(src) { return { readingId: src.readingId, uptime: src.uptime, utcTime: src.utcTime, isBreak: src.isBreak }; } //# sourceMappingURL=utc-assigner.js.map