mirror of
https://github.com/beestat/app.git
synced 2025-05-24 02:14:03 -04:00
395 lines
11 KiB
JavaScript
395 lines
11 KiB
JavaScript
/**
|
|
* Air Quality card. Shows a chart with comfort profiles, occupancy, and air
|
|
* quality data.
|
|
*
|
|
* @param {number} thermostat_id The thermostat_id this card is displaying
|
|
* data for
|
|
*/
|
|
beestat.component.card.air_quality_summary = function(thermostat_id) {
|
|
var self = this;
|
|
|
|
this.thermostat_id_ = thermostat_id;
|
|
|
|
/*
|
|
* When a setting is changed clear all of the data. Then rerender which will
|
|
* trigger the loading state. Also do this when the cache changes.
|
|
*
|
|
* Debounce so that multiple setting changes don't re-trigger the same
|
|
* event. This fires on the trailing edge so that all changes are accounted
|
|
* for when rerendering.
|
|
*/
|
|
var change_function = beestat.debounce(function() {
|
|
self.rerender();
|
|
}, 10);
|
|
|
|
beestat.dispatcher.addEventListener(
|
|
[
|
|
'setting.air_quality_summary_range_type',
|
|
'setting.air_quality_summary_range_dynamic',
|
|
'cache.data.air_quality_summary'
|
|
],
|
|
change_function
|
|
);
|
|
|
|
beestat.component.card.apply(this, arguments);
|
|
};
|
|
beestat.extend(beestat.component.card.air_quality_summary, beestat.component.card);
|
|
|
|
beestat.component.card.air_quality_summary.prototype.rerender_on_resize_ = true;
|
|
|
|
/**
|
|
* Decorate
|
|
*
|
|
* @param {rocket.Elements} parent
|
|
*/
|
|
beestat.component.card.air_quality_summary.prototype.decorate_contents_ = function(parent) {
|
|
var self = this;
|
|
|
|
var container = $.createElement('div').style({
|
|
'position': 'relative'
|
|
});
|
|
parent.appendChild(container);
|
|
|
|
var chart_container = $.createElement('div');
|
|
container.appendChild(chart_container);
|
|
|
|
this.decorate_chart_(chart_container);
|
|
|
|
var thermostat = beestat.cache.thermostat[this.thermostat_id_];
|
|
|
|
var required_begin;
|
|
var required_end;
|
|
if (beestat.setting('air_quality_summary_range_type') === 'dynamic') {
|
|
required_begin = moment()
|
|
.subtract(
|
|
beestat.setting('air_quality_summary_range_dynamic'),
|
|
'day'
|
|
)
|
|
.second(0);
|
|
|
|
required_end = moment()
|
|
.subtract(1, 'hour')
|
|
.second(0);
|
|
} else {
|
|
required_begin = moment(
|
|
beestat.setting('air_quality_summary_range_static_begin') + ' 00:00:00'
|
|
);
|
|
required_end = moment(
|
|
beestat.setting('air_quality_summary_range_static_end') + ' 23:59:59'
|
|
);
|
|
}
|
|
|
|
// Don't go before there's data.
|
|
required_begin = moment.max(
|
|
required_begin,
|
|
moment(thermostat.data_begin)
|
|
);
|
|
|
|
// Don't go after now.
|
|
required_end = moment.min(
|
|
required_end,
|
|
moment().subtract(1, 'hour')
|
|
);
|
|
|
|
required_end = moment.max(
|
|
required_end,
|
|
moment(thermostat.data_begin)
|
|
);
|
|
|
|
/**
|
|
* If the needed data exists in the database and the runtime_sensor
|
|
* cache is empty, then query the data. If the needed data does not exist in
|
|
* the database, check every 2 seconds until it does.
|
|
*/
|
|
if (beestat.thermostat.data_synced(this.thermostat_id_, required_begin, required_end) === true) {
|
|
if (beestat.cache.data.air_quality_summary === undefined) {
|
|
this.show_loading_('Fetching');
|
|
|
|
var value;
|
|
var operator;
|
|
|
|
if (beestat.setting('air_quality_summary_range_type') === 'dynamic') {
|
|
value = required_begin.format();
|
|
operator = '>=';
|
|
} else {
|
|
value = [
|
|
required_begin.format(),
|
|
required_end.format()
|
|
];
|
|
operator = 'between';
|
|
}
|
|
|
|
var api_call = new beestat.api();
|
|
Object.values(beestat.cache.sensor).forEach(function(sensor) {
|
|
if (
|
|
sensor.thermostat_id === self.thermostat_id_ &&
|
|
sensor.type === 'thermostat'
|
|
) {
|
|
api_call.add_call(
|
|
'runtime_sensor',
|
|
'read',
|
|
{
|
|
'attributes': {
|
|
'sensor_id': sensor.sensor_id,
|
|
'timestamp': {
|
|
'value': value,
|
|
'operator': operator
|
|
}
|
|
}
|
|
},
|
|
'runtime_sensor_' + sensor.sensor_id
|
|
);
|
|
}
|
|
});
|
|
|
|
api_call.set_callback(function(response) {
|
|
var runtime_sensors = [];
|
|
for (var alias in response) {
|
|
var r = response[alias];
|
|
runtime_sensors = runtime_sensors.concat(r);
|
|
}
|
|
beestat.cache.set('data.air_quality_summary', runtime_sensors);
|
|
});
|
|
|
|
api_call.send();
|
|
} else if (this.has_data_() === false) {
|
|
chart_container.style('filter', 'blur(3px)');
|
|
var no_data = $.createElement('div');
|
|
no_data.style({
|
|
'position': 'absolute',
|
|
'top': 0,
|
|
'left': 0,
|
|
'width': '100%',
|
|
'height': '100%',
|
|
'display': 'flex',
|
|
'flex-direction': 'column',
|
|
'justify-content': 'center',
|
|
'text-align': 'center'
|
|
});
|
|
no_data.innerText('No data to display');
|
|
container.appendChild(no_data);
|
|
}
|
|
} else {
|
|
this.show_loading_('Syncing');
|
|
window.setTimeout(function() {
|
|
new beestat.api()
|
|
.add_call(
|
|
'thermostat',
|
|
'read_id',
|
|
{
|
|
'attributes': {
|
|
'inactive': 0
|
|
}
|
|
},
|
|
'thermostat'
|
|
)
|
|
.set_callback(function(response) {
|
|
beestat.cache.set('thermostat', response);
|
|
self.rerender();
|
|
})
|
|
.send();
|
|
}, 2000);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Decorate chart
|
|
*
|
|
* @param {rocket.Elements} parent
|
|
*/
|
|
beestat.component.card.air_quality_summary.prototype.decorate_chart_ = function(parent) {
|
|
const gradient = beestat.style.generate_gradient(
|
|
[
|
|
beestat.style.hex_to_rgb(beestat.style.color.green.base),
|
|
beestat.style.hex_to_rgb(beestat.style.color.yellow.base),
|
|
beestat.style.hex_to_rgb(beestat.style.color.red.base)
|
|
],
|
|
100
|
|
);
|
|
|
|
var grid_data = {};
|
|
for (var runtime_sensor_id in beestat.cache.data.air_quality_summary) {
|
|
var runtime_sensor = beestat.cache.data.air_quality_summary[runtime_sensor_id];
|
|
var key = moment(runtime_sensor.timestamp).format('d_H');
|
|
if (grid_data[key] === undefined) {
|
|
grid_data[key] = [];
|
|
}
|
|
if (runtime_sensor.air_quality !== null) {
|
|
grid_data[key].push(runtime_sensor.air_quality);
|
|
}
|
|
}
|
|
|
|
var table = $.createElement('table');
|
|
table.style({
|
|
'table-layout': 'fixed',
|
|
'border-collapse': 'collapse',
|
|
'width': '100%'
|
|
});
|
|
|
|
var tr;
|
|
var td;
|
|
|
|
tr = $.createElement('tr');
|
|
tr.appendChild($.createElement('td').style({'width': '50px'}));
|
|
table.appendChild(tr);
|
|
|
|
var days_of_week = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
|
|
var day;
|
|
var hour;
|
|
|
|
// Header row
|
|
for (hour = 0; hour < 24; hour++) {
|
|
var meridiem = hour >= 12 ? 'p' : 'a';
|
|
var new_hour = (hour % 12) || 12;
|
|
|
|
tr.appendChild(
|
|
$.createElement('td')
|
|
.innerText(new_hour + (window.innerWidth > 700 ? meridiem : ''))
|
|
.style({
|
|
'text-align': 'center',
|
|
'font-size': beestat.style.font_size.small
|
|
})
|
|
);
|
|
}
|
|
|
|
for (day = 0; day < 7; day++) {
|
|
tr = $.createElement('tr');
|
|
table.appendChild(tr);
|
|
|
|
td = $.createElement('td')
|
|
.innerHTML(days_of_week[day]);
|
|
tr.appendChild(td);
|
|
|
|
for (hour = 0; hour < 24; hour++) {
|
|
var cell_value = grid_data[day + '_' + hour];
|
|
td = $.createElement('td');
|
|
var background = beestat.style.color.bluegray.light;
|
|
|
|
if (cell_value !== undefined) {
|
|
var average;
|
|
if (grid_data[day + '_' + hour].length > 0) {
|
|
average = grid_data[day + '_' + hour].reduce(function(a, b) {
|
|
return a + b;
|
|
}) / grid_data[day + '_' + hour].length;
|
|
} else {
|
|
average = 0;
|
|
}
|
|
|
|
td.setAttribute('title', Math.round(average));
|
|
|
|
// I am normalizing Air Quality between 0 and 100.
|
|
const max_average = 100;
|
|
if (average < 1) {
|
|
background = beestat.style.color.bluegray.light;
|
|
} else if (average <= max_average) {
|
|
background = beestat.style.rgb_to_hex(gradient[
|
|
Math.floor(average / (max_average / gradient.length))
|
|
]);
|
|
} else {
|
|
background = beestat.style.rgb_to_hex(gradient[gradient.length - 1]);
|
|
}
|
|
}
|
|
|
|
td.style({
|
|
'height': '20px',
|
|
'background': background
|
|
});
|
|
|
|
tr.appendChild(td);
|
|
}
|
|
}
|
|
|
|
parent.appendChild(table);
|
|
};
|
|
|
|
/**
|
|
* Decorate the menu
|
|
*
|
|
* @param {rocket.Elements} parent
|
|
*/
|
|
beestat.component.card.air_quality_summary.prototype.decorate_top_right_ = function(parent) {
|
|
var menu = (new beestat.component.menu()).render(parent);
|
|
|
|
menu.add_menu_item(new beestat.component.menu_item()
|
|
.set_text('Past 1 Week')
|
|
.set_icon('numeric_1_box')
|
|
.set_callback(function() {
|
|
if (
|
|
beestat.setting('air_quality_summary_range_dynamic') !== 7 ||
|
|
beestat.setting('air_quality_summary_range_type') !== 'dynamic'
|
|
) {
|
|
beestat.cache.delete('data.air_quality_summary');
|
|
beestat.setting({
|
|
'air_quality_summary_range_dynamic': 7,
|
|
'air_quality_summary_range_type': 'dynamic'
|
|
});
|
|
}
|
|
}));
|
|
|
|
menu.add_menu_item(new beestat.component.menu_item()
|
|
.set_text('Past 4 Weeks')
|
|
.set_icon('numeric_4_box')
|
|
.set_callback(function() {
|
|
if (
|
|
beestat.setting('air_quality_summary_range_dynamic') !== 28 ||
|
|
beestat.setting('air_quality_summary_range_type') !== 'dynamic'
|
|
) {
|
|
beestat.cache.delete('data.air_quality_summary');
|
|
beestat.setting({
|
|
'air_quality_summary_range_dynamic': 28,
|
|
'air_quality_summary_range_type': 'dynamic'
|
|
});
|
|
}
|
|
}));
|
|
|
|
menu.add_menu_item(new beestat.component.menu_item()
|
|
.set_text('Help')
|
|
.set_icon('help_circle')
|
|
.set_callback(function() {
|
|
window.open('https://doc.beestat.io/25a1a894a7f4432ead5831bef48770bd');
|
|
}));
|
|
};
|
|
|
|
/**
|
|
* Whether or not there is data to display on the chart.
|
|
*
|
|
* @return {boolean} Whether or not there is data to display on the chart.
|
|
*/
|
|
beestat.component.card.air_quality_summary.prototype.has_data_ = function() {
|
|
return beestat.cache.data.air_quality_summary &&
|
|
beestat.cache.data.air_quality_summary.length > 0;
|
|
};
|
|
|
|
/**
|
|
* Get the title of the card.
|
|
*
|
|
* @return {string} Title
|
|
*/
|
|
beestat.component.card.air_quality_summary.prototype.get_title_ = function() {
|
|
return 'Air Quality Summary';
|
|
};
|
|
|
|
/**
|
|
* Get the subtitle of the card.
|
|
*
|
|
* @return {string} Subtitle
|
|
*/
|
|
beestat.component.card.air_quality_summary.prototype.get_subtitle_ = function() {
|
|
if (beestat.setting('air_quality_summary_range_type') === 'dynamic') {
|
|
var s = ((beestat.setting('air_quality_summary_range_dynamic') / 7) > 1) ? 's' : '';
|
|
|
|
return 'Past ' +
|
|
(beestat.setting('air_quality_summary_range_dynamic') / 7) +
|
|
' week' +
|
|
s;
|
|
}
|
|
|
|
var begin = moment(beestat.setting('air_quality_summary_range_static_begin'))
|
|
.format('MMM D, YYYY');
|
|
var end = moment(beestat.setting('air_quality_summary_range_static_end'))
|
|
.format('MMM D, YYYY');
|
|
|
|
return begin + ' to ' + end;
|
|
};
|