/*
* Copyright © 2016-2019 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export default angular.module('thingsboard.api.time', [])
.factory('timeService', TimeService)
.name;
const SECOND = 1000;
const MINUTE = 60 * SECOND;
const HOUR = 60 * MINUTE;
const DAY = 24 * HOUR;
const MIN_INTERVAL = SECOND;
const MAX_INTERVAL = 365 * 20 * DAY;
const MIN_LIMIT = 10;
//const AVG_LIMIT = 200;
//const MAX_LIMIT = 500;
/*@ngInject*/
function TimeService($translate, $http, $q, types) {
var predefIntervals;
var maxDatapointsLimit;
var service = {
loadMaxDatapointsLimit: loadMaxDatapointsLimit,
minIntervalLimit: minIntervalLimit,
maxIntervalLimit: maxIntervalLimit,
boundMinInterval: boundMinInterval,
boundMaxInterval: boundMaxInterval,
getIntervals: getIntervals,
matchesExistingInterval: matchesExistingInterval,
boundToPredefinedInterval: boundToPredefinedInterval,
defaultTimewindow: defaultTimewindow,
toHistoryTimewindow: toHistoryTimewindow,
createSubscriptionTimewindow: createSubscriptionTimewindow,
getMaxDatapointsLimit: function () {
return maxDatapointsLimit;
},
getMinDatapointsLimit: function () {
return MIN_LIMIT;
}
}
return service;
function loadMaxDatapointsLimit() {
var deferred = $q.defer();
var url = '/api/dashboard/maxDatapointsLimit';
$http.get(url, {ignoreLoading: true}).then(function success(response) {
maxDatapointsLimit = response.data;
if (!maxDatapointsLimit || maxDatapointsLimit <= MIN_LIMIT) {
maxDatapointsLimit = MIN_LIMIT + 1;
}
deferred.resolve();
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function minIntervalLimit(timewindow) {
var min = timewindow / 500;
return boundMinInterval(min);
}
function avgInterval(timewindow) {
var avg = timewindow / 200;
return boundMinInterval(avg);
}
function maxIntervalLimit(timewindow) {
var max = timewindow / MIN_LIMIT;
return boundMaxInterval(max);
}
function boundMinInterval(min) {
return toBound(min, MIN_INTERVAL, MAX_INTERVAL, MIN_INTERVAL);
}
function boundMaxInterval(max) {
return toBound(max, MIN_INTERVAL, MAX_INTERVAL, MAX_INTERVAL);
}
function toBound(value, min, max, defValue) {
if (angular.isDefined(value)) {
value = Math.max(value, min);
value = Math.min(value, max);
return value;
} else {
return defValue;
}
}
function getIntervals(min, max) {
min = boundMinInterval(min);
max = boundMaxInterval(max);
var intervals = [];
initPredefIntervals();
for (var i in predefIntervals) {
var interval = predefIntervals[i];
if (interval.value >= min && interval.value <= max) {
intervals.push(interval);
}
}
return intervals;
}
function initPredefIntervals() {
if (!predefIntervals) {
predefIntervals = [
{
name: $translate.instant('timeinterval.seconds-interval', {seconds: 1}, 'messageformat'),
value: 1 * SECOND
},
{
name: $translate.instant('timeinterval.seconds-interval', {seconds: 5}, 'messageformat'),
value: 5 * SECOND
},
{
name: $translate.instant('timeinterval.seconds-interval', {seconds: 10}, 'messageformat'),
value: 10 * SECOND
},
{
name: $translate.instant('timeinterval.seconds-interval', {seconds: 15}, 'messageformat'),
value: 15 * SECOND
},
{
name: $translate.instant('timeinterval.seconds-interval', {seconds: 30}, 'messageformat'),
value: 30 * SECOND
},
{
name: $translate.instant('timeinterval.minutes-interval', {minutes: 1}, 'messageformat'),
value: 1 * MINUTE
},
{
name: $translate.instant('timeinterval.minutes-interval', {minutes: 2}, 'messageformat'),
value: 2 * MINUTE
},
{
name: $translate.instant('timeinterval.minutes-interval', {minutes: 5}, 'messageformat'),
value: 5 * MINUTE
},
{
name: $translate.instant('timeinterval.minutes-interval', {minutes: 10}, 'messageformat'),
value: 10 * MINUTE
},
{
name: $translate.instant('timeinterval.minutes-interval', {minutes: 15}, 'messageformat'),
value: 15 * MINUTE
},
{
name: $translate.instant('timeinterval.minutes-interval', {minutes: 30}, 'messageformat'),
value: 30 * MINUTE
},
{
name: $translate.instant('timeinterval.hours-interval', {hours: 1}, 'messageformat'),
value: 1 * HOUR
},
{
name: $translate.instant('timeinterval.hours-interval', {hours: 2}, 'messageformat'),
value: 2 * HOUR
},
{
name: $translate.instant('timeinterval.hours-interval', {hours: 5}, 'messageformat'),
value: 5 * HOUR
},
{
name: $translate.instant('timeinterval.hours-interval', {hours: 10}, 'messageformat'),
value: 10 * HOUR
},
{
name: $translate.instant('timeinterval.hours-interval', {hours: 12}, 'messageformat'),
value: 12 * HOUR
},
{
name: $translate.instant('timeinterval.days-interval', {days: 1}, 'messageformat'),
value: 1 * DAY
},
{
name: $translate.instant('timeinterval.days-interval', {days: 7}, 'messageformat'),
value: 7 * DAY
},
{
name: $translate.instant('timeinterval.days-interval', {days: 30}, 'messageformat'),
value: 30 * DAY
}
];
}
}
function matchesExistingInterval(min, max, intervalMs) {
var intervals = getIntervals(min, max);
for (var i in intervals) {
var interval = intervals[i];
if (intervalMs === interval.value) {
return true;
}
}
return false;
}
function boundToPredefinedInterval(min, max, intervalMs) {
var intervals = getIntervals(min, max);
var minDelta = MAX_INTERVAL;
var boundedInterval = intervalMs || min;
var matchedInterval;
for (var i in intervals) {
var interval = intervals[i];
var delta = Math.abs(interval.value - boundedInterval);
if (delta < minDelta) {
matchedInterval = interval;
minDelta = delta;
}
}
boundedInterval = matchedInterval.value;
return boundedInterval;
}
function defaultTimewindow() {
var currentTime = (new Date).getTime();
var timewindow = {
displayValue: "",
selectedTab: 0,
realtime: {
interval: SECOND,
timewindowMs: MINUTE // 1 min by default
},
history: {
historyType: 0,
interval: SECOND,
timewindowMs: MINUTE, // 1 min by default
fixedTimewindow: {
startTimeMs: currentTime - DAY, // 1 day by default
endTimeMs: currentTime
}
},
aggregation: {
type: types.aggregation.avg.value,
limit: Math.floor(maxDatapointsLimit / 2)
}
}
return timewindow;
}
function toHistoryTimewindow(timewindow, startTimeMs, endTimeMs) {
var interval = 0;
if (timewindow.history) {
interval = timewindow.history.interval;
} else if (timewindow.realtime) {
interval = timewindow.realtime.interval;
}
var aggType;
var limit;
if (timewindow.aggregation) {
aggType = timewindow.aggregation.type || types.aggregation.avg.value;
limit = timewindow.aggregation.limit || maxDatapointsLimit;
} else {
aggType = types.aggregation.avg.value;
limit = maxDatapointsLimit;
}
var historyTimewindow = {
history: {
fixedTimewindow: {
startTimeMs: startTimeMs,
endTimeMs: endTimeMs
},
interval: boundIntervalToTimewindow(endTimeMs - startTimeMs, interval, types.aggregation.avg.value)
},
aggregation: {
type: aggType,
limit: limit
}
}
return historyTimewindow;
}
function createSubscriptionTimewindow(timewindow, stDiff, stateData) {
var subscriptionTimewindow = {
fixedWindow: null,
realtimeWindowMs: null,
aggregation: {
interval: SECOND,
limit: maxDatapointsLimit,
type: types.aggregation.avg.value
}
};
var aggTimewindow = 0;
if (stateData) {
subscriptionTimewindow.aggregation = {
interval: SECOND,
limit: maxDatapointsLimit,
type: types.aggregation.none.value,
stateData: true
};
} else {
subscriptionTimewindow.aggregation = {
interval: SECOND,
limit: maxDatapointsLimit,
type: types.aggregation.avg.value
};
}
if (angular.isDefined(timewindow.aggregation) && !stateData) {
subscriptionTimewindow.aggregation = {
type: timewindow.aggregation.type || types.aggregation.avg.value,
limit: timewindow.aggregation.limit || maxDatapointsLimit
};
}
if (angular.isDefined(timewindow.realtime)) {
subscriptionTimewindow.realtimeWindowMs = timewindow.realtime.timewindowMs;
subscriptionTimewindow.aggregation.interval =
boundIntervalToTimewindow(subscriptionTimewindow.realtimeWindowMs, timewindow.realtime.interval,
subscriptionTimewindow.aggregation.type);
subscriptionTimewindow.startTs = (new Date).getTime() + stDiff - subscriptionTimewindow.realtimeWindowMs;
var startDiff = subscriptionTimewindow.startTs % subscriptionTimewindow.aggregation.interval;
aggTimewindow = subscriptionTimewindow.realtimeWindowMs;
if (startDiff) {
subscriptionTimewindow.startTs -= startDiff;
aggTimewindow += subscriptionTimewindow.aggregation.interval;
}
} else if (angular.isDefined(timewindow.history)) {
if (angular.isDefined(timewindow.history.timewindowMs)) {
var currentTime = (new Date).getTime();
subscriptionTimewindow.fixedWindow = {
startTimeMs: currentTime - timewindow.history.timewindowMs,
endTimeMs: currentTime
}
aggTimewindow = timewindow.history.timewindowMs;
} else {
subscriptionTimewindow.fixedWindow = {
startTimeMs: timewindow.history.fixedTimewindow.startTimeMs,
endTimeMs: timewindow.history.fixedTimewindow.endTimeMs
}
aggTimewindow = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs;
}
subscriptionTimewindow.startTs = subscriptionTimewindow.fixedWindow.startTimeMs;
subscriptionTimewindow.aggregation.interval =
boundIntervalToTimewindow(aggTimewindow, timewindow.history.interval, subscriptionTimewindow.aggregation.type);
}
var aggregation = subscriptionTimewindow.aggregation;
aggregation.timeWindow = aggTimewindow;
if (aggregation.type !== types.aggregation.none.value) {
aggregation.limit = Math.ceil(aggTimewindow / subscriptionTimewindow.aggregation.interval);
}
return subscriptionTimewindow;
}
function boundIntervalToTimewindow(timewindow, intervalMs, aggType) {
if (aggType === types.aggregation.none.value) {
return SECOND;
} else {
var min = minIntervalLimit(timewindow);
var max = maxIntervalLimit(timewindow);
if (intervalMs) {
return toBound(intervalMs, min, max, intervalMs);
} else {
return boundToPredefinedInterval(min, max, avgInterval(timewindow));
}
}
}
}