mirror of
https://github.com/beestat/app.git
synced 2025-05-24 02:14:03 -04:00
Significantly improved data downloading.
This commit is contained in:
parent
8054a10329
commit
5cb676970c
@ -728,10 +728,12 @@ class runtime extends cora\api {
|
||||
* Download all data that exists for a specific thermostat.
|
||||
*
|
||||
* @param int $thermostat_id
|
||||
* @param string $download_begin Optional; the date to begin the download.
|
||||
* @param string $download_end Optional; the date to end the download.
|
||||
* @param string $download_begin The timestamp to begin the download. If a
|
||||
* time zone is not specified, UTC is assumed.
|
||||
* @param string $download_end The timestamp to end the download. If a time
|
||||
* zone is not specified, UTC is assumed.
|
||||
*/
|
||||
public function download($thermostat_id, $download_begin = null, $download_end = null) {
|
||||
public function download($thermostat_id, $download_begin, $download_end) {
|
||||
set_time_limit(120);
|
||||
|
||||
$this->user_lock($thermostat_id);
|
||||
@ -743,17 +745,25 @@ class runtime extends cora\api {
|
||||
$thermostat['ecobee_thermostat_id']
|
||||
);
|
||||
|
||||
if($download_begin === null) {
|
||||
$download_begin = strtotime($thermostat['first_connected']);
|
||||
} else {
|
||||
$download_begin = strtotime($download_begin);
|
||||
// Allow for inverted arguments.
|
||||
if (strtotime($download_end) < strtotime($download_begin)) {
|
||||
$temp = $download_begin;
|
||||
$download_begin = $download_end;
|
||||
$download_end = $temp;
|
||||
}
|
||||
|
||||
if($download_end === null) {
|
||||
$download_end = time();
|
||||
} else {
|
||||
$download_end = strtotime($download_end);
|
||||
}
|
||||
// Clamp
|
||||
$download_begin = strtotime($download_begin);
|
||||
$download_begin = max(strtotime($thermostat['first_connected']), $download_begin);
|
||||
$download_begin = min(time(), $download_begin);
|
||||
|
||||
$download_end = strtotime($download_end);
|
||||
$download_end = max(strtotime($thermostat['first_connected']), $download_end);
|
||||
$download_end = min(time(), $download_end);
|
||||
|
||||
// Round begin/end down to the next 5 minutes.
|
||||
$download_begin = floor($download_begin / 300) * 300;
|
||||
$download_end = floor($download_end / 300) * 300;
|
||||
|
||||
$chunk_begin = $download_begin;
|
||||
$chunk_end = $download_begin;
|
||||
@ -771,7 +781,7 @@ class runtime extends cora\api {
|
||||
[
|
||||
'thermostat_id' => $thermostat_id,
|
||||
'timestamp' => [
|
||||
'value' => [date('Y-m-d H:i:s', $chunk_begin), date('Y-m-d H:i:s', $chunk_end)] ,
|
||||
'value' => [date('Y-m-d H:i:s', $chunk_begin), date('Y-m-d H:i:s', $chunk_end)],
|
||||
'operator' => 'between'
|
||||
]
|
||||
],
|
||||
@ -808,19 +818,22 @@ class runtime extends cora\api {
|
||||
$needs_header = false;
|
||||
}
|
||||
|
||||
$runtime_thermostats_by_timestamp = [];
|
||||
foreach($runtime_thermostats as $runtime_thermostat) {
|
||||
unset($runtime_thermostat['runtime_thermostat_id']);
|
||||
unset($runtime_thermostat['thermostat_id']);
|
||||
|
||||
$runtime_thermostat['timestamp'] = $this->get_local_datetime(
|
||||
$runtime_thermostat['timestamp'],
|
||||
$thermostat['time_zone']
|
||||
);
|
||||
|
||||
// Return temperatures in a human-readable format.
|
||||
foreach(['indoor_temperature', 'outdoor_temperature', 'setpoint_heat', 'setpoint_cool'] as $key) {
|
||||
if($runtime_thermostat[$key] !== null) {
|
||||
$runtime_thermostat[$key] /= 10;
|
||||
if(
|
||||
isset($thermostat['setting']['temperature_unit']) === true &&
|
||||
$thermostat['setting']['temperature_unit'] === '°C'
|
||||
) {
|
||||
$runtime_thermostat[$key] =
|
||||
round(($runtime_thermostat[$key] - 32) * (5 / 9), 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -833,7 +846,30 @@ class runtime extends cora\api {
|
||||
$runtime_thermostat['climate_runtime_thermostat_text_id'] = $runtime_thermostat_texts[$runtime_thermostat['climate_runtime_thermostat_text_id']]['value'];
|
||||
}
|
||||
|
||||
$bytes += fputcsv($output, $runtime_thermostat);
|
||||
$strtotime = strtotime($runtime_thermostat['timestamp']);
|
||||
$runtime_thermostats_by_timestamp[$strtotime] = $runtime_thermostat;
|
||||
|
||||
// Now remove it since it's not used.
|
||||
unset($runtime_thermostats_by_timestamp[$strtotime]['timestamp']);
|
||||
}
|
||||
|
||||
$current_timestamp = $chunk_begin;
|
||||
while($current_timestamp <= $chunk_end) {
|
||||
$local_datetime = $this->get_local_datetime(
|
||||
date('Y-m-d H:i:s', $current_timestamp),
|
||||
$thermostat['time_zone']
|
||||
);
|
||||
|
||||
if(isset($runtime_thermostats_by_timestamp[$current_timestamp]) === true) {
|
||||
$csv_row = array_merge(
|
||||
[$local_datetime],
|
||||
$runtime_thermostats_by_timestamp[$current_timestamp]
|
||||
);
|
||||
} else {
|
||||
$csv_row = [$local_datetime];
|
||||
}
|
||||
$bytes += fputcsv($output, $csv_row);
|
||||
$current_timestamp += 300;
|
||||
}
|
||||
|
||||
$chunk_begin = $chunk_end;
|
||||
|
@ -100,6 +100,10 @@ beestat.component.button.prototype.decorate_ = function(parent) {
|
||||
this.button_.addEventListener('click', function() {
|
||||
self.dispatchEvent('click');
|
||||
});
|
||||
|
||||
this.button_.addEventListener('mousedown', function() {
|
||||
self.dispatchEvent('mousedown');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -2,7 +2,24 @@
|
||||
* Input parent class.
|
||||
*/
|
||||
beestat.component.input.text = function() {
|
||||
var self = this;
|
||||
|
||||
this.input_ = $.createElement('input');
|
||||
|
||||
// Add these up top so they don't get re-added on rerender.
|
||||
this.input_.addEventListener('focus', function() {
|
||||
self.input_.style({
|
||||
'background': beestat.style.color.bluegray.dark
|
||||
});
|
||||
});
|
||||
|
||||
this.input_.addEventListener('blur', function() {
|
||||
self.dispatchEvent('blur');
|
||||
self.input_.style({
|
||||
'background': beestat.style.color.bluegray.light
|
||||
});
|
||||
});
|
||||
|
||||
beestat.component.apply(this, arguments);
|
||||
};
|
||||
beestat.extend(beestat.component.input.text, beestat.component.input);
|
||||
@ -15,8 +32,6 @@ beestat.component.input.text.prototype.rerender_on_breakpoint_ = false;
|
||||
* @param {rocket.Elements} parent
|
||||
*/
|
||||
beestat.component.input.text.prototype.decorate_ = function(parent) {
|
||||
var self = this;
|
||||
|
||||
this.input_
|
||||
.setAttribute('type', 'text')
|
||||
.style({
|
||||
@ -56,19 +71,6 @@ beestat.component.input.text.prototype.decorate_ = function(parent) {
|
||||
(new beestat.component.icon(this.icon_).set_size(16).set_color('#fff')).render(icon_container);
|
||||
}
|
||||
|
||||
this.input_.addEventListener('focus', function() {
|
||||
self.input_.style({
|
||||
'background': beestat.style.color.bluegray.dark
|
||||
});
|
||||
});
|
||||
|
||||
this.input_.addEventListener('blur', function() {
|
||||
self.dispatchEvent('blur');
|
||||
self.input_.style({
|
||||
'background': beestat.style.color.bluegray.light
|
||||
});
|
||||
});
|
||||
|
||||
if (this.value_ !== undefined) {
|
||||
this.input_.value(this.value_);
|
||||
}
|
||||
|
@ -3,125 +3,212 @@
|
||||
*/
|
||||
beestat.component.modal.download_data = function() {
|
||||
beestat.component.modal.apply(this, arguments);
|
||||
this.state_.range_begin = moment().hour(0)
|
||||
.minute(0)
|
||||
.second(0)
|
||||
.millisecond(0);
|
||||
this.state_.range_end = this.state_.range_begin.clone();
|
||||
};
|
||||
beestat.extend(beestat.component.modal.download_data, beestat.component.modal);
|
||||
|
||||
/**
|
||||
* Decorate.
|
||||
*
|
||||
* @param {rocket.Elements} parent
|
||||
*/
|
||||
beestat.component.modal.download_data.prototype.decorate_contents_ = function(parent) {
|
||||
parent.appendChild($.createElement('p').innerHTML('Beestat stores, at a minimum, the past year of raw thermostat logs. Select a date range to download.'));
|
||||
this.decorate_range_(parent);
|
||||
this.decorate_presets_(parent);
|
||||
this.decorate_error_(parent);
|
||||
|
||||
// Fire off this event once to get everything updated.
|
||||
this.dispatchEvent('range_change');
|
||||
};
|
||||
|
||||
/**
|
||||
* Decorate range inputs.
|
||||
*
|
||||
* @param {rocket.Elements} parent
|
||||
*/
|
||||
beestat.component.modal.download_data.prototype.decorate_range_ = function(parent) {
|
||||
var self = this;
|
||||
|
||||
parent.appendChild($.createElement('p').innerHTML('Choose a custom download range.'));
|
||||
(new beestat.component.title('Date Range')).render(parent);
|
||||
|
||||
self.state_.download_data_time_count = 1;
|
||||
|
||||
// Time count
|
||||
var time_count = new beestat.component.input.text()
|
||||
var range_begin = new beestat.component.input.text()
|
||||
.set_style({
|
||||
'width': 75,
|
||||
'width': 110,
|
||||
'text-align': 'center',
|
||||
'border-bottom': '2px solid ' + beestat.style.color.lightblue.base
|
||||
})
|
||||
.set_attribute({
|
||||
'maxlength': 10
|
||||
})
|
||||
.set_icon('pound')
|
||||
.set_value(self.state_.download_data_time_count);
|
||||
.set_icon('calendar')
|
||||
.set_value(this.state_.range_begin.format('M/D/YYYY'));
|
||||
|
||||
time_count.addEventListener('blur', function() {
|
||||
self.state_.download_data_time_count =
|
||||
parseInt(this.get_value(), 10) || 1;
|
||||
range_begin.addEventListener('blur', function() {
|
||||
self.state_.range_begin = moment(this.get_value());
|
||||
self.dispatchEvent('range_change');
|
||||
});
|
||||
|
||||
// Button groups
|
||||
var options = {
|
||||
'download_data_time_period': [
|
||||
'day',
|
||||
'week',
|
||||
'month',
|
||||
'year',
|
||||
'all'
|
||||
]
|
||||
};
|
||||
var range_end = new beestat.component.input.text()
|
||||
.set_style({
|
||||
'width': 110,
|
||||
'text-align': 'center',
|
||||
'border-bottom': '2px solid ' + beestat.style.color.lightblue.base
|
||||
})
|
||||
.set_attribute({
|
||||
'maxlength': 10
|
||||
})
|
||||
.set_icon('calendar')
|
||||
.set_value(this.state_.range_end.format('M/D/YYYY'));
|
||||
|
||||
var button_groups = {};
|
||||
range_end.addEventListener('blur', function() {
|
||||
self.state_.range_end = moment(this.get_value());
|
||||
self.dispatchEvent('range_change');
|
||||
});
|
||||
|
||||
this.selected_buttons_ = {};
|
||||
for (let key in options) {
|
||||
let current_type = 'month';
|
||||
// Update the inputs if the range changes.
|
||||
this.addEventListener('range_change', function() {
|
||||
if (self.state_.range_begin.isValid() === true) {
|
||||
range_begin.set_value(self.state_.range_begin.format('M/D/YYYY'));
|
||||
}
|
||||
|
||||
let button_group = new beestat.component.button_group();
|
||||
options[key].forEach(function(value) {
|
||||
let text = value.replace('download_data_', '')
|
||||
.charAt(0)
|
||||
.toUpperCase() +
|
||||
value.slice(1) +
|
||||
(
|
||||
(
|
||||
key === 'download_data_time_period' &&
|
||||
value !== 'all'
|
||||
) ? 's' : ''
|
||||
);
|
||||
if (self.state_.range_end.isValid() === true) {
|
||||
range_end.set_value(self.state_.range_end.format('M/D/YYYY'));
|
||||
}
|
||||
});
|
||||
|
||||
let button = new beestat.component.button()
|
||||
.set_background_hover_color(beestat.style.color.lightblue.base)
|
||||
.set_text_color('#fff')
|
||||
.set_text(text)
|
||||
.addEventListener('click', function() {
|
||||
if (key === 'download_data_time_period') {
|
||||
if (value === 'all') {
|
||||
time_count.set_value('∞').disable();
|
||||
} else if (time_count.get_value() === '∞') {
|
||||
time_count
|
||||
.set_value(self.state_.download_data_time_count || '1')
|
||||
.enable();
|
||||
time_count.dispatchEvent('blur');
|
||||
}
|
||||
}
|
||||
var span;
|
||||
|
||||
if (current_type !== value) {
|
||||
this.set_background_color(beestat.style.color.lightblue.base);
|
||||
if (self.selected_buttons_[key] !== undefined) {
|
||||
self.selected_buttons_[key]
|
||||
.set_background_color(beestat.style.color.bluegray.base);
|
||||
}
|
||||
self.selected_buttons_[key] = this;
|
||||
self.state_[key] = value;
|
||||
current_type = value;
|
||||
}
|
||||
});
|
||||
|
||||
if (current_type === value) {
|
||||
if (
|
||||
key === 'download_data_time_period' &&
|
||||
value === 'all'
|
||||
) {
|
||||
time_count.set_value('∞').disable();
|
||||
}
|
||||
|
||||
button.set_background_color(beestat.style.color.lightblue.base);
|
||||
self.state_[key] = value;
|
||||
self.selected_buttons_[key] = button;
|
||||
} else {
|
||||
button.set_background_color(beestat.style.color.bluegray.base);
|
||||
}
|
||||
|
||||
button_group.add_button(button);
|
||||
});
|
||||
button_groups[key] = button_group;
|
||||
}
|
||||
|
||||
// Display it all
|
||||
var row;
|
||||
var column;
|
||||
|
||||
(new beestat.component.title('Time Period')).render(parent);
|
||||
row = $.createElement('div').addClass('row');
|
||||
var row = $.createElement('div').addClass('row');
|
||||
parent.appendChild(row);
|
||||
column = $.createElement('div').addClass(['column column_2']);
|
||||
var column = $.createElement('div').addClass(['column column_12']);
|
||||
row.appendChild(column);
|
||||
time_count.render(column);
|
||||
column = $.createElement('div').addClass(['column column_10']);
|
||||
|
||||
span = $.createElement('span').style('display', 'inline-block');
|
||||
range_begin.render(span);
|
||||
column.appendChild(span);
|
||||
|
||||
span = $.createElement('span')
|
||||
.style({
|
||||
'display': 'inline-block',
|
||||
'margin-left': beestat.style.size.gutter,
|
||||
'margin-right': beestat.style.size.gutter
|
||||
})
|
||||
.innerText('to');
|
||||
column.appendChild(span);
|
||||
|
||||
span = $.createElement('span').style('display', 'inline-block');
|
||||
range_end.render(span);
|
||||
column.appendChild(span);
|
||||
};
|
||||
|
||||
/**
|
||||
* Decorate preset options.
|
||||
*
|
||||
* @param {rocket.Elements} parent
|
||||
*/
|
||||
beestat.component.modal.download_data.prototype.decorate_presets_ = function(parent) {
|
||||
var self = this;
|
||||
|
||||
(new beestat.component.title('Presets')).render(parent);
|
||||
|
||||
var row = $.createElement('div').addClass('row');
|
||||
parent.appendChild(row);
|
||||
var column = $.createElement('div').addClass(['column column_12']);
|
||||
row.appendChild(column);
|
||||
button_groups.download_data_time_period.render(column);
|
||||
|
||||
var thermostat = beestat.cache.thermostat[beestat.setting('thermostat_id')];
|
||||
|
||||
var now = moment().hour(0)
|
||||
.minute(0)
|
||||
.second(0)
|
||||
.millisecond(0);
|
||||
|
||||
var presets = [
|
||||
{
|
||||
'label': 'Today',
|
||||
'range_begin': now.clone(),
|
||||
'range_end': now.clone(),
|
||||
'button': new beestat.component.button()
|
||||
},
|
||||
{
|
||||
'label': 'This Week',
|
||||
'range_begin': now.clone().startOf('week'),
|
||||
'range_end': now.clone(),
|
||||
'button': new beestat.component.button()
|
||||
},
|
||||
{
|
||||
'label': 'This Month',
|
||||
'range_begin': now.clone().startOf('month'),
|
||||
'range_end': now.clone(),
|
||||
'button': new beestat.component.button()
|
||||
},
|
||||
{
|
||||
'label': 'All Time',
|
||||
'range_begin': moment.max(moment(thermostat.first_connected), now.clone().subtract(1, 'year')),
|
||||
'range_end': now.clone(),
|
||||
'button': new beestat.component.button()
|
||||
}
|
||||
];
|
||||
|
||||
var button_group = new beestat.component.button_group();
|
||||
presets.forEach(function(preset) {
|
||||
preset.button
|
||||
.set_background_color(beestat.style.color.bluegray.base)
|
||||
.set_background_hover_color(beestat.style.color.lightblue.base)
|
||||
.set_text_color('#fff')
|
||||
.set_text(preset.label)
|
||||
.addEventListener('mousedown', function() {
|
||||
self.state_.range_begin = preset.range_begin;
|
||||
self.state_.range_end = preset.range_end;
|
||||
self.dispatchEvent('range_change');
|
||||
});
|
||||
button_group.add_button(preset.button);
|
||||
});
|
||||
|
||||
// Highlight the preset if the current date range matches.
|
||||
this.addEventListener('range_change', function() {
|
||||
presets.forEach(function(preset) {
|
||||
if (
|
||||
preset.range_begin.isSame(self.state_.range_begin) &&
|
||||
preset.range_end.isSame(self.state_.range_end)
|
||||
) {
|
||||
preset.button.set_background_color(beestat.style.color.lightblue.base);
|
||||
} else {
|
||||
preset.button.set_background_color(beestat.style.color.bluegray.base);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
button_group.render(column);
|
||||
};
|
||||
|
||||
/**
|
||||
* Decorate the error area.
|
||||
*
|
||||
* @param {rocket.Elements} parent
|
||||
*/
|
||||
beestat.component.modal.download_data.prototype.decorate_error_ = function(parent) {
|
||||
var self = this;
|
||||
|
||||
var div = $.createElement('div').style('color', beestat.style.color.red.base);
|
||||
|
||||
// Display errors as necessary.
|
||||
this.addEventListener('range_change', function() {
|
||||
div.innerHTML('');
|
||||
if (self.state_.range_begin.isValid() === false) {
|
||||
div.appendChild($.createElement('div').innerText('Invalid begin date.'));
|
||||
}
|
||||
if (self.state_.range_end.isValid() === false) {
|
||||
div.appendChild($.createElement('div').innerText('Invalid end date.'));
|
||||
}
|
||||
});
|
||||
|
||||
parent.appendChild(div);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -154,25 +241,23 @@ beestat.component.modal.download_data.prototype.get_buttons_ = function() {
|
||||
.set_background_color(beestat.style.color.green.base)
|
||||
.set_background_hover_color(beestat.style.color.green.light)
|
||||
.set_text_color('#fff')
|
||||
.set_text('Download Data')
|
||||
.set_text('Download')
|
||||
.addEventListener('click', function() {
|
||||
var download_begin;
|
||||
var download_end = null;
|
||||
if (self.state_.download_data_time_period === 'all') {
|
||||
download_begin = null;
|
||||
var range_begin;
|
||||
var range_end;
|
||||
if (self.state_.range_end.isBefore(self.state_.range_begin) === true) {
|
||||
range_begin = self.state_.range_end;
|
||||
range_end = self.state_.range_begin;
|
||||
} else {
|
||||
download_begin = moment().utc()
|
||||
.subtract(
|
||||
self.state_.download_data_time_count,
|
||||
self.state_.download_data_time_period
|
||||
)
|
||||
.format('YYYY-MM-DD HH:mm:ss');
|
||||
range_begin = self.state_.range_begin;
|
||||
range_end = self.state_.range_end;
|
||||
}
|
||||
|
||||
var download_arguments = {
|
||||
'thermostat_id': beestat.setting('thermostat_id'),
|
||||
'download_begin': download_begin,
|
||||
'download_end': download_end
|
||||
'download_begin': range_begin.format(),
|
||||
'download_end': range_end.hour(23).minute(55)
|
||||
.format()
|
||||
};
|
||||
|
||||
window.location.href = '/api/?resource=runtime&method=download&arguments=' + JSON.stringify(download_arguments) + '&api_key=' + beestat.api.api_key;
|
||||
|
Loading…
x
Reference in New Issue
Block a user