@iotile/iotile-device
Version:
A typescript library for interfacing with IOTile BLE devices
216 lines • 9.35 kB
JavaScript
"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