mirror of
https://github.com/beestat/app.git
synced 2025-05-24 02:14:03 -04:00
Removed "json_" prefixes from all columns and converted columns to actual JSON types. Also removed all converged columns and converted contents to regular columns.
1439 lines
45 KiB
JavaScript
Executable File
1439 lines
45 KiB
JavaScript
Executable File
/**
|
|
* Recent activity card. Shows a graph similar to what ecobee shows with the
|
|
* runtime info for a recent period of time.
|
|
*/
|
|
beestat.component.card.recent_activity = function() {
|
|
beestat.component.card.apply(this, arguments);
|
|
};
|
|
beestat.extend(beestat.component.card.recent_activity, beestat.component.card);
|
|
|
|
beestat.component.card.recent_activity.optional_series = [
|
|
'compressor_heat_1',
|
|
'compressor_heat_2',
|
|
'compressor_cool_1',
|
|
'compressor_cool_2',
|
|
'auxiliary_heat_1',
|
|
'auxiliary_heat_2',
|
|
'fan',
|
|
'dehumidifier',
|
|
'economizer',
|
|
'humidifier',
|
|
'ventilator'
|
|
];
|
|
|
|
beestat.component.card.recent_activity.calendar_events = [
|
|
'calendar_event_home',
|
|
'calendar_event_away',
|
|
'calendar_event_sleep',
|
|
'calendar_event_vacation',
|
|
'calendar_event_smarthome',
|
|
'calendar_event_smartaway',
|
|
'calendar_event_smartrecovery',
|
|
'calendar_event_hold',
|
|
'calendar_event_quicksave',
|
|
'calendar_event_other'
|
|
];
|
|
|
|
/**
|
|
* Decorate
|
|
*
|
|
* @param {rocket.ELements} parent
|
|
*/
|
|
beestat.component.card.recent_activity.prototype.decorate_contents_ = function(parent) {
|
|
var self = this;
|
|
|
|
var thermostat = beestat.cache.thermostat[beestat.setting('thermostat_id')];
|
|
|
|
this.chart_ = new beestat.component.chart();
|
|
var series = this.get_series_();
|
|
|
|
this.chart_.options.chart.backgroundColor = beestat.style.color.bluegray.base;
|
|
this.chart_.options.exporting.filename = thermostat.name + ' - Recent Activity';
|
|
this.chart_.options.exporting.chartOptions.title.text = this.get_title_();
|
|
this.chart_.options.exporting.chartOptions.subtitle.text = this.get_subtitle_();
|
|
|
|
var current_day;
|
|
var current_hour;
|
|
this.chart_.options.xAxis = {
|
|
'categories': series.x.chart_data,
|
|
'type': 'datetime',
|
|
'lineColor': beestat.style.color.bluegray.light,
|
|
'min': series.x.chart_data[0],
|
|
'max': series.x.chart_data[series.x.chart_data.length - 1],
|
|
'minRange': 21600000,
|
|
'tickLength': 0,
|
|
'gridLineWidth': 0,
|
|
'labels': {
|
|
'style': {'color': beestat.style.color.gray.base},
|
|
'formatter': function() {
|
|
var m = moment(this.value);
|
|
var hour = m.format('ha');
|
|
var day = m.format('ddd');
|
|
|
|
var label_parts = [];
|
|
if (day !== current_day) {
|
|
label_parts.push(day);
|
|
}
|
|
if (hour !== current_hour) {
|
|
label_parts.push(hour);
|
|
}
|
|
|
|
current_hour = hour;
|
|
current_day = day;
|
|
|
|
return label_parts.join(' ');
|
|
}
|
|
}
|
|
};
|
|
|
|
// Add some space for the top of the graph.
|
|
this.y_max_ += 30;
|
|
|
|
// Because higcharts isn't respecting the tickInterval parameter...seems to
|
|
// have to do with the secondary axis; as removing it makes it work a lot
|
|
// better.
|
|
var tick_positions = [];
|
|
var tick_interval = (thermostat.temperature_unit === '°F') ? 10 : 5;
|
|
var current_tick_position =
|
|
Math.floor(this.y_min_ / tick_interval) * tick_interval;
|
|
while (current_tick_position <= this.y_max_) {
|
|
tick_positions.push(current_tick_position);
|
|
current_tick_position += tick_interval;
|
|
}
|
|
|
|
this.chart_.options.yAxis = [
|
|
// Temperature
|
|
{
|
|
// 'alignTicks': false, // Uncommenting this will allow the humidity series to line up but it will also force the y-axis to be a bit larger. For example, a y min of 17 will get set to a min of 0 instead of 15 because the spacing is set to 20.
|
|
'gridLineColor': beestat.style.color.bluegray.light,
|
|
'gridLineDashStyle': 'longdash',
|
|
'title': {'text': null},
|
|
'labels': {
|
|
'style': {'color': beestat.style.color.gray.base},
|
|
'formatter': function() {
|
|
return this.value + thermostat.temperature_unit;
|
|
}
|
|
},
|
|
'tickPositions': tick_positions
|
|
},
|
|
|
|
// Top bars
|
|
{
|
|
'height': 100,
|
|
'min': 0,
|
|
'max': 100,
|
|
'gridLineWidth': 0,
|
|
'title': {'text': null},
|
|
'labels': {'enabled': false}
|
|
},
|
|
|
|
// Humidity
|
|
{
|
|
'alignTicks': false,
|
|
'gridLineColor': null,
|
|
'tickInterval': 10,
|
|
// 'gridLineDashStyle': 'longdash',
|
|
'opposite': true,
|
|
'title': {'text': null},
|
|
'labels': {
|
|
'style': {'color': beestat.style.color.gray.base},
|
|
'formatter': function() {
|
|
return this.value + '%';
|
|
}
|
|
},
|
|
|
|
/*
|
|
* If you set a min/max highcharts always shows the axis. Setting these
|
|
* attributes prevents the "always show" logic and the 0-100 is achieved
|
|
* with this set of parameters.
|
|
* https://github.com/highcharts/highcharts/issues/3403
|
|
*/
|
|
'min': 0,
|
|
'minRange': 100,
|
|
'ceiling': 100
|
|
}
|
|
];
|
|
|
|
this.chart_.options.tooltip = {
|
|
'shared': true,
|
|
'useHTML': true,
|
|
'borderWidth': 0,
|
|
'shadow': false,
|
|
'backgroundColor': null,
|
|
'followPointer': true,
|
|
'crosshairs': {
|
|
'width': 1,
|
|
'zIndex': 100,
|
|
'color': beestat.style.color.gray.light,
|
|
'dashStyle': 'shortDot',
|
|
'snap': false
|
|
},
|
|
'positioner': function(tooltip_width, tooltip_height, point) {
|
|
return beestat.component.chart.tooltip_positioner(
|
|
self.chart_.get_chart(),
|
|
tooltip_width,
|
|
tooltip_height,
|
|
point
|
|
);
|
|
},
|
|
'formatter': function() {
|
|
var self = this;
|
|
|
|
var sections = [];
|
|
|
|
// HVAC Mode
|
|
var system_mode;
|
|
var system_mode_color;
|
|
|
|
switch (series.system_mode.data[self.x]) {
|
|
case 'auto':
|
|
system_mode = 'Auto';
|
|
system_mode_color = beestat.style.color.gray.base;
|
|
break;
|
|
case 'heat':
|
|
system_mode = 'Heat';
|
|
system_mode_color = beestat.series.compressor_heat_1.color;
|
|
break;
|
|
case 'cool':
|
|
system_mode = 'Cool';
|
|
system_mode_color = beestat.series.compressor_cool_1.color;
|
|
break;
|
|
case 'off':
|
|
system_mode = 'Off';
|
|
system_mode_color = beestat.style.color.gray.base;
|
|
break;
|
|
case 'auxiliary_heat':
|
|
system_mode = 'Aux';
|
|
system_mode_color = beestat.series.auxiliary_heat_1.color;
|
|
break;
|
|
}
|
|
|
|
var section_1 = [];
|
|
sections.push(section_1);
|
|
|
|
if (system_mode !== undefined) {
|
|
section_1.push({
|
|
'label': 'Mode',
|
|
'value': system_mode,
|
|
'color': system_mode_color
|
|
});
|
|
}
|
|
|
|
// Calendar Event / Comfort Profile
|
|
var event;
|
|
var event_color;
|
|
|
|
for (var i = 0; i < beestat.component.card.recent_activity.calendar_events.length; i++) {
|
|
var calendar_event = beestat.component.card.recent_activity.calendar_events[i];
|
|
if (series[calendar_event].data[self.x] !== null) {
|
|
event = beestat.series[calendar_event].name;
|
|
event_color = beestat.series[calendar_event].color;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (event !== undefined) {
|
|
section_1.push({
|
|
'label': 'Comfort Profile',
|
|
'value': event,
|
|
'color': event_color
|
|
});
|
|
}
|
|
|
|
var section_2 = [];
|
|
sections.push(section_2);
|
|
|
|
[
|
|
'setpoint_heat',
|
|
'setpoint_cool',
|
|
'indoor_temperature',
|
|
'outdoor_temperature',
|
|
'indoor_humidity',
|
|
'outdoor_humidity'
|
|
].forEach(function(series_code) {
|
|
var value;
|
|
|
|
if (series_code === 'setpoint_cool') {
|
|
return; // Grab it when doing setpoint_heat
|
|
} else if (series_code === 'setpoint_heat') {
|
|
if (
|
|
series[series_code].data[self.x] === null
|
|
) {
|
|
return;
|
|
}
|
|
|
|
switch (series.system_mode.data[self.x]) {
|
|
case 'heat':
|
|
if (series.setpoint_heat.data[self.x] === null) {
|
|
return;
|
|
}
|
|
value = beestat.temperature({
|
|
'temperature': series.setpoint_heat.data[self.x],
|
|
'convert': false,
|
|
'units': true
|
|
});
|
|
break;
|
|
case 'cool':
|
|
if (series.setpoint_cool.data[self.x] === null) {
|
|
return;
|
|
}
|
|
value = beestat.temperature({
|
|
'temperature': series.setpoint_cool.data[self.x],
|
|
'convert': false,
|
|
'units': true
|
|
});
|
|
break;
|
|
case 'auto':
|
|
if (
|
|
series.setpoint_heat.data[self.x] === null ||
|
|
series.setpoint_cool.data[self.x] === null
|
|
) {
|
|
return;
|
|
}
|
|
value = beestat.temperature({
|
|
'temperature': series.setpoint_heat.data[self.x],
|
|
'convert': false,
|
|
'units': true
|
|
});
|
|
value += ' - ';
|
|
value += beestat.temperature({
|
|
'temperature': series.setpoint_cool.data[self.x],
|
|
'convert': false,
|
|
'units': true
|
|
});
|
|
break;
|
|
default:
|
|
return;
|
|
break;
|
|
}
|
|
} else if (
|
|
series_code === 'indoor_humidity' ||
|
|
series_code === 'outdoor_humidity'
|
|
) {
|
|
if (series[series_code].data[self.x] === null) {
|
|
return;
|
|
}
|
|
value = series[series_code].data[self.x] + '%';
|
|
} else {
|
|
if (series[series_code].data[self.x] === null) {
|
|
return;
|
|
}
|
|
value = beestat.temperature({
|
|
'temperature': series[series_code].data[self.x],
|
|
'convert': false,
|
|
'units': true
|
|
});
|
|
}
|
|
|
|
section_2.push({
|
|
'label': beestat.series[series_code].name,
|
|
'value': value,
|
|
'color': beestat.style.color.gray.light
|
|
});
|
|
});
|
|
|
|
var section_3 = [];
|
|
sections.push(section_3);
|
|
|
|
beestat.component.card.recent_activity.optional_series.forEach(function(series_code) {
|
|
if (
|
|
series[series_code].data[self.x] !== undefined &&
|
|
series[series_code].data[self.x] !== null
|
|
) {
|
|
section_3.push({
|
|
'label': beestat.series[series_code].name,
|
|
'value': beestat.time(series[series_code].durations[self.x].seconds),
|
|
'color': beestat.series[series_code].color
|
|
});
|
|
}
|
|
});
|
|
|
|
return beestat.component.chart.tooltip_formatter(
|
|
moment(this.x).format('ddd, MMM D @ h:mma'),
|
|
sections
|
|
);
|
|
}
|
|
};
|
|
|
|
this.chart_.options.series = [];
|
|
|
|
beestat.component.card.recent_activity.calendar_events.forEach(function(calendar_event) {
|
|
self.chart_.options.series.push({
|
|
'id': calendar_event,
|
|
'linkedTo': (calendar_event !== 'calendar_event_home') ? 'calendar_event_home' : undefined,
|
|
'data': series[calendar_event].chart_data,
|
|
'yAxis': 1,
|
|
'marker': {
|
|
'enabled': false,
|
|
'states': {'hover': {'enabled': false}}
|
|
},
|
|
'name': 'Comfort Profile',
|
|
'type': 'line',
|
|
'color': beestat.series[calendar_event].color,
|
|
'lineWidth': 5,
|
|
'linecap': 'square',
|
|
'states': {'hover': {'lineWidthPlus': 0}}
|
|
});
|
|
});
|
|
|
|
if (series.compressor_cool_1.enabled === true) {
|
|
this.chart_.options.series.push({
|
|
'id': 'compressor_cool_1',
|
|
'data': series.compressor_cool_1.chart_data,
|
|
'yAxis': 1,
|
|
'marker': {
|
|
'enabled': false,
|
|
'states': {'hover': {'enabled': false}}
|
|
},
|
|
'name': 'Cool',
|
|
'type': 'line',
|
|
'color': beestat.series.compressor_cool_1.color,
|
|
'lineWidth': 10,
|
|
'linecap': 'square',
|
|
'states': {'hover': {'lineWidthPlus': 0}}
|
|
});
|
|
}
|
|
|
|
if (series.compressor_cool_2.enabled === true) {
|
|
this.chart_.options.series.push({
|
|
'data': series.compressor_cool_2.chart_data,
|
|
'linkedTo': 'compressor_cool_1',
|
|
'yAxis': 1,
|
|
'marker': {
|
|
'enabled': false,
|
|
'states': {'hover': {'enabled': false}}
|
|
},
|
|
'name': beestat.series.compressor_cool_2.name,
|
|
'type': 'line',
|
|
'color': beestat.series.compressor_cool_2.color,
|
|
'lineWidth': 10,
|
|
'linecap': 'square',
|
|
'states': {'hover': {'lineWidthPlus': 0}}
|
|
});
|
|
}
|
|
|
|
if (series.compressor_heat_1.enabled === true) {
|
|
this.chart_.options.series.push({
|
|
'id': 'compressor_heat_1',
|
|
'data': series.compressor_heat_1.chart_data,
|
|
'yAxis': 1,
|
|
'marker': {
|
|
'enabled': false,
|
|
'states': {'hover': {'enabled': false}}
|
|
},
|
|
'name': 'Heat',
|
|
'type': 'line',
|
|
'color': beestat.series.compressor_heat_1.color,
|
|
'lineWidth': 10,
|
|
'linecap': 'square',
|
|
'states': {'hover': {'lineWidthPlus': 0}}
|
|
});
|
|
}
|
|
|
|
if (series.compressor_heat_2.enabled === true) {
|
|
this.chart_.options.series.push({
|
|
'linkedTo': 'compressor_heat_1',
|
|
'data': series.compressor_heat_2.chart_data,
|
|
'yAxis': 1,
|
|
'marker': {
|
|
'enabled': false,
|
|
'states': {'hover': {'enabled': false}}
|
|
},
|
|
'name': beestat.series.compressor_heat_2.name,
|
|
'type': 'line',
|
|
'color': beestat.series.compressor_heat_2.color,
|
|
'lineWidth': 10,
|
|
'linecap': 'square',
|
|
'states': {'hover': {'lineWidthPlus': 0}}
|
|
});
|
|
}
|
|
|
|
[
|
|
'auxiliary_heat_1',
|
|
'auxiliary_heat_2'
|
|
].forEach(function(equipment) {
|
|
if (series[equipment].enabled === true) {
|
|
self.chart_.options.series.push({
|
|
'data': series[equipment].chart_data,
|
|
'yAxis': 1,
|
|
'marker': {
|
|
'enabled': false,
|
|
'states': {'hover': {'enabled': false}}
|
|
},
|
|
'name': beestat.series[equipment].name,
|
|
'type': 'line',
|
|
'color': beestat.series[equipment].color,
|
|
'lineWidth': 10,
|
|
'linecap': 'square',
|
|
'states': {'hover': {'lineWidthPlus': 0}}
|
|
});
|
|
}
|
|
});
|
|
|
|
if (series.fan.enabled === true) {
|
|
this.chart_.options.series.push({
|
|
'data': series.fan.chart_data,
|
|
'yAxis': 1,
|
|
'marker': {
|
|
'enabled': false,
|
|
'states': {'hover': {'enabled': false}}
|
|
},
|
|
'name': beestat.series.fan.name,
|
|
'type': 'line',
|
|
'color': beestat.series.fan.color,
|
|
'lineWidth': 5,
|
|
'linecap': 'square',
|
|
'states': {'hover': {'lineWidthPlus': 0}}
|
|
});
|
|
}
|
|
|
|
[
|
|
'dehumidifier',
|
|
'economizer',
|
|
'humidifier',
|
|
'ventilator'
|
|
].forEach(function(equipment) {
|
|
if (series[equipment].enabled === true) {
|
|
self.chart_.options.series.push({
|
|
'data': series[equipment].chart_data,
|
|
'yAxis': 1,
|
|
'marker': {
|
|
'enabled': false,
|
|
'states': {'hover': {'enabled': false}}
|
|
},
|
|
'name': beestat.series[equipment].name,
|
|
'type': 'line',
|
|
'color': beestat.series[equipment].color,
|
|
'lineWidth': 5,
|
|
'linecap': 'square',
|
|
'states': {'hover': {'lineWidthPlus': 0}}
|
|
});
|
|
}
|
|
});
|
|
|
|
this.chart_.options.series.push({
|
|
'id': 'indoor_humidity',
|
|
'data': series.indoor_humidity.chart_data,
|
|
'yAxis': 2,
|
|
'name': beestat.series.indoor_humidity.name,
|
|
'marker': {
|
|
'enabled': false,
|
|
'states': {'hover': {'enabled': false}}
|
|
},
|
|
'type': 'spline',
|
|
'dashStyle': 'DashDot',
|
|
'visible': false,
|
|
'lineWidth': 1,
|
|
'color': beestat.series.indoor_humidity.color,
|
|
'states': {'hover': {'lineWidthPlus': 0}},
|
|
|
|
/*
|
|
* Weird HighCharts bug...
|
|
* https://stackoverflow.com/questions/48374093/highcharts-highstock-line-change-to-area-bug
|
|
* https://github.com/highcharts/highcharts/issues/766
|
|
*/
|
|
'linecap': 'square'
|
|
});
|
|
|
|
this.chart_.options.series.push({
|
|
'id': 'outdoor_humidity',
|
|
'data': series.outdoor_humidity.chart_data,
|
|
'yAxis': 2,
|
|
'name': beestat.series.outdoor_humidity.name,
|
|
'marker': {
|
|
'enabled': false,
|
|
'states': {'hover': {'enabled': false}}
|
|
},
|
|
'type': 'spline',
|
|
'dashStyle': 'DashDot',
|
|
'visible': false,
|
|
'lineWidth': 1,
|
|
'color': beestat.series.outdoor_humidity.color,
|
|
'states': {'hover': {'lineWidthPlus': 0}},
|
|
|
|
/*
|
|
* Weird HighCharts bug...
|
|
* https://stackoverflow.com/questions/48374093/highcharts-highstock-line-change-to-area-bug
|
|
* https://github.com/highcharts/highcharts/issues/766
|
|
*/
|
|
'linecap': 'square'
|
|
});
|
|
|
|
this.chart_.options.series.push({
|
|
'data': series.indoor_temperature.chart_data,
|
|
'yAxis': 0,
|
|
'name': beestat.series.indoor_temperature.name,
|
|
'marker': {
|
|
'enabled': false,
|
|
'states': {'hover': {'enabled': false}}
|
|
},
|
|
'type': 'spline',
|
|
'lineWidth': 2,
|
|
'color': beestat.series.indoor_temperature.color,
|
|
'states': {'hover': {'lineWidthPlus': 0}},
|
|
|
|
/*
|
|
* Weird HighCharts bug...
|
|
* https://stackoverflow.com/questions/48374093/highcharts-highstock-line-change-to-area-bug
|
|
* https://github.com/highcharts/highcharts/issues/766
|
|
*/
|
|
'linecap': 'square'
|
|
});
|
|
|
|
this.chart_.options.series.push({
|
|
'color': beestat.series.outdoor_temperature.color,
|
|
'data': series.outdoor_temperature.chart_data,
|
|
// 'zones': beestat.component.chart.get_outdoor_temperature_zones(),
|
|
'yAxis': 0,
|
|
'name': beestat.series.outdoor_temperature.name,
|
|
'marker': {
|
|
'enabled': false,
|
|
'states': {'hover': {'enabled': false}}
|
|
},
|
|
'type': 'spline',
|
|
'dashStyle': 'ShortDash',
|
|
'lineWidth': 1,
|
|
'states': {'hover': {'lineWidthPlus': 0}}
|
|
});
|
|
|
|
this.chart_.options.series.push({
|
|
'data': series.setpoint_heat.chart_data,
|
|
'id': 'setpoint_heat',
|
|
'yAxis': 0,
|
|
'marker': {
|
|
'enabled': false,
|
|
'states': {'hover': {'enabled': false}}
|
|
},
|
|
'name': beestat.series.setpoint_heat.name,
|
|
'type': 'line',
|
|
'color': beestat.series.setpoint_heat.color,
|
|
'lineWidth': 1,
|
|
'states': {'hover': {'lineWidthPlus': 0}},
|
|
'step': 'right'
|
|
});
|
|
|
|
this.chart_.options.series.push({
|
|
'data': series.setpoint_cool.chart_data,
|
|
'yAxis': 0,
|
|
'marker': {
|
|
'enabled': false,
|
|
'states': {'hover': {'enabled': false}}
|
|
},
|
|
'name': beestat.series.setpoint_cool.name,
|
|
'type': 'line',
|
|
'color': beestat.series.setpoint_cool.color,
|
|
'lineWidth': 1,
|
|
'states': {'hover': {'lineWidthPlus': 0}},
|
|
'step': 'right'
|
|
});
|
|
|
|
this.chart_.render(parent);
|
|
|
|
this.show_loading_('Syncing Recent Activity');
|
|
|
|
/*
|
|
* If the data is available, then get the data if we don't already have it
|
|
* loaded. If the data is not available, poll until it becomes available.
|
|
*/
|
|
if (this.data_available_() === true) {
|
|
if (beestat.cache.runtime_thermostat.length === 0) {
|
|
this.get_data_();
|
|
} else {
|
|
this.hide_loading_();
|
|
}
|
|
} else {
|
|
var poll_interval = 10000;
|
|
|
|
beestat.add_poll_interval(poll_interval);
|
|
beestat.dispatcher.addEventListener('poll.recent_activity_load', function() {
|
|
if (self.data_available_() === true) {
|
|
beestat.remove_poll_interval(poll_interval);
|
|
beestat.dispatcher.removeEventListener('poll.recent_activity_load');
|
|
self.get_data_();
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Decorate the menu
|
|
*
|
|
* @param {rocket.Elements} parent
|
|
*/
|
|
beestat.component.card.recent_activity.prototype.decorate_top_right_ = function(parent) {
|
|
var self = this;
|
|
|
|
var menu = (new beestat.component.menu()).render(parent);
|
|
|
|
menu.add_menu_item(new beestat.component.menu_item()
|
|
.set_text('Past 1 Day')
|
|
.set_icon('numeric_1_box')
|
|
.set_callback(function() {
|
|
if (
|
|
beestat.setting('recent_activity_time_count') !== 1 ||
|
|
beestat.setting('recent_activity_time_period') !== 'day'
|
|
) {
|
|
beestat.setting({
|
|
'recent_activity_time_count': 1,
|
|
'recent_activity_time_period': 'day'
|
|
});
|
|
|
|
/*
|
|
* Rerender; the timeout lets the menu close immediately without being
|
|
* blocked by the time it takes to rerender the chart.
|
|
*/
|
|
setTimeout(function() {
|
|
self.rerender();
|
|
}, 0);
|
|
}
|
|
}));
|
|
|
|
menu.add_menu_item(new beestat.component.menu_item()
|
|
.set_text('Past 3 Days')
|
|
.set_icon('numeric_3_box')
|
|
.set_callback(function() {
|
|
if (
|
|
beestat.setting('recent_activity_time_count') !== 3 ||
|
|
beestat.setting('recent_activity_time_period') !== 'day'
|
|
) {
|
|
beestat.setting({
|
|
'recent_activity_time_count': 3,
|
|
'recent_activity_time_period': 'day'
|
|
});
|
|
|
|
setTimeout(function() {
|
|
self.rerender();
|
|
}, 0);
|
|
}
|
|
}));
|
|
|
|
menu.add_menu_item(new beestat.component.menu_item()
|
|
.set_text('Past 7 Days')
|
|
.set_icon('numeric_7_box')
|
|
.set_callback(function() {
|
|
if (
|
|
beestat.setting('recent_activity_time_count') !== 7 ||
|
|
beestat.setting('recent_activity_time_period') !== 'day'
|
|
) {
|
|
beestat.setting({
|
|
'recent_activity_time_count': 7,
|
|
'recent_activity_time_period': 'day'
|
|
});
|
|
setTimeout(function() {
|
|
self.rerender();
|
|
}, 0);
|
|
}
|
|
}));
|
|
|
|
menu.add_menu_item(new beestat.component.menu_item()
|
|
.set_text('Download Chart')
|
|
.set_icon('download')
|
|
.set_callback(function() {
|
|
self.chart_.get_chart().exportChartLocal();
|
|
}));
|
|
|
|
menu.add_menu_item(new beestat.component.menu_item()
|
|
.set_text('Reset Zoom')
|
|
.set_icon('magnify_minus')
|
|
.set_callback(function() {
|
|
self.chart_.get_chart().zoomOut();
|
|
}));
|
|
|
|
menu.add_menu_item(new beestat.component.menu_item()
|
|
.set_text('Help')
|
|
.set_icon('help_circle')
|
|
.set_callback(function() {
|
|
(new beestat.component.modal.help_recent_activity()).render();
|
|
}));
|
|
};
|
|
|
|
/**
|
|
* Get all of the series data.
|
|
*
|
|
* @return {object} The series data.
|
|
*/
|
|
beestat.component.card.recent_activity.prototype.get_series_ = function() {
|
|
var self = this;
|
|
|
|
var thermostat = beestat.cache.thermostat[beestat.setting('thermostat_id')];
|
|
|
|
/*
|
|
* The more data that gets shown the larger the smoothing factor should be
|
|
* (less points, smoother graph).
|
|
*/
|
|
var smoothing_factor = beestat.setting('recent_activity_time_count') * 3;
|
|
|
|
this.y_min_ = Infinity;
|
|
this.y_max_ = -Infinity;
|
|
|
|
/*
|
|
* The chart_data property is what Highcharts uses. The data property is the
|
|
* same data indexed by the x value to make it easy to access.
|
|
*/
|
|
var series = {
|
|
'x': {
|
|
'enabled': true,
|
|
'chart_data': [],
|
|
'data': {}
|
|
},
|
|
'setpoint_heat': {
|
|
'enabled': true,
|
|
'chart_data': [],
|
|
'data': {}
|
|
},
|
|
'setpoint_cool': {
|
|
'enabled': true,
|
|
'chart_data': [],
|
|
'data': {}
|
|
},
|
|
'outdoor_temperature': {
|
|
'enabled': true,
|
|
'chart_data': [],
|
|
'data': {}
|
|
},
|
|
'indoor_temperature': {
|
|
'enabled': true,
|
|
'chart_data': [],
|
|
'data': {}
|
|
},
|
|
'indoor_humidity': {
|
|
'enabled': true,
|
|
'chart_data': [],
|
|
'data': {}
|
|
},
|
|
'outdoor_humidity': {
|
|
'enabled': true,
|
|
'chart_data': [],
|
|
'data': {}
|
|
},
|
|
'system_mode': {
|
|
'enabled': true,
|
|
'chart_data': [],
|
|
'data': {}
|
|
}
|
|
};
|
|
|
|
// Initialize the optional series.
|
|
beestat.component.card.recent_activity.optional_series.forEach(function(optional_series) {
|
|
series[optional_series] = {
|
|
'enabled': false,
|
|
'chart_data': [],
|
|
'data': {},
|
|
'durations': {}
|
|
};
|
|
});
|
|
|
|
// Initialize the calendar event series.
|
|
beestat.component.card.recent_activity.calendar_events.forEach(function(calendar_event) {
|
|
series[calendar_event] = {
|
|
'enabled': false,
|
|
'chart_data': [],
|
|
'data': {}
|
|
};
|
|
});
|
|
|
|
/*
|
|
* Overrides the %10 smoothing for when there is missing data. Basically just
|
|
* ensures that the graph starts back up right away instead of waiting for a
|
|
* 10th data point.
|
|
*/
|
|
var previous_indoor_temperature_value = null;
|
|
var previous_outdoor_temperature_value = null;
|
|
var previous_indoor_humidity_value = null;
|
|
var previous_outdoor_humidity_value = null;
|
|
|
|
var min_x = moment()
|
|
.subtract(
|
|
beestat.setting('recent_activity_time_count'),
|
|
beestat.setting('recent_activity_time_period')
|
|
)
|
|
.valueOf();
|
|
|
|
/*
|
|
* This creates a distinct object for each chunk of runtime so the total on
|
|
* time can be computed for any given segment.
|
|
*/
|
|
var durations = {};
|
|
|
|
beestat.cache.runtime_thermostat.forEach(function(runtime_thermostat, i) {
|
|
// if (runtime_thermostat.ecobee_thermostat_id !== thermostat.ecobee_thermostat_id) {
|
|
// return;
|
|
// }
|
|
//
|
|
|
|
if (runtime_thermostat.compressor_mode === 'heat') {
|
|
runtime_thermostat.compressor_heat_1 = runtime_thermostat.compressor_1;
|
|
runtime_thermostat.compressor_heat_2 = runtime_thermostat.compressor_2;
|
|
runtime_thermostat.compressor_cool_1 = 0;
|
|
runtime_thermostat.compressor_cool_2 = 0;
|
|
} else if (runtime_thermostat.compressor_mode === 'cool') {
|
|
runtime_thermostat.compressor_heat_1 = 0;
|
|
runtime_thermostat.compressor_heat_2 = 0;
|
|
runtime_thermostat.compressor_cool_1 = runtime_thermostat.compressor_1;
|
|
runtime_thermostat.compressor_cool_2 = runtime_thermostat.compressor_2;
|
|
} else if (runtime_thermostat.compressor_mode === 'off') {
|
|
runtime_thermostat.compressor_heat_1 = 0;
|
|
runtime_thermostat.compressor_heat_2 = 0;
|
|
runtime_thermostat.compressor_cool_1 = 0;
|
|
runtime_thermostat.compressor_cool_2 = 0;
|
|
} else {
|
|
runtime_thermostat.compressor_heat_1 = null;
|
|
runtime_thermostat.compressor_heat_2 = null;
|
|
runtime_thermostat.compressor_cool_1 = null;
|
|
runtime_thermostat.compressor_cool_2 = null;
|
|
}
|
|
|
|
runtime_thermostat.humidifier = 0;
|
|
runtime_thermostat.dehumidifier = 0;
|
|
runtime_thermostat.ventilator = 0;
|
|
runtime_thermostat.economizer = 0;
|
|
|
|
// The string includes +00:00 as the UTC offset but moment knows what time
|
|
// zone my PC is in...or at least it has a guess. This means that beestat
|
|
// graphs can now show up in local time instead of thermostat time.
|
|
var x = moment(runtime_thermostat.timestamp).valueOf();
|
|
if (x < min_x) {
|
|
return;
|
|
}
|
|
|
|
series.x.chart_data.push(x);
|
|
|
|
var original_durations = {};
|
|
if (runtime_thermostat.compressor_heat_2 > 0) {
|
|
original_durations.compressor_heat_1 = runtime_thermostat.compressor_heat_1;
|
|
runtime_thermostat.compressor_heat_1 = runtime_thermostat.compressor_heat_2;
|
|
}
|
|
// TODO DO THIS FOR AUX
|
|
// TODO DO THIS FOR COOL
|
|
|
|
beestat.component.card.recent_activity.optional_series.forEach(function(series_code) {
|
|
if (durations[series_code] === undefined) {
|
|
durations[series_code] = [{'seconds': 0}];
|
|
}
|
|
|
|
// if (series_code === 'compressor_heat_1') {
|
|
// runtime_thermostat
|
|
// }
|
|
|
|
if (
|
|
runtime_thermostat[series_code] !== null &&
|
|
runtime_thermostat[series_code] > 0
|
|
) {
|
|
var value;
|
|
switch (series_code) {
|
|
case 'fan':
|
|
value = 70;
|
|
break;
|
|
case 'dehumidifier':
|
|
case 'economizer':
|
|
case 'humidifier':
|
|
case 'ventilator':
|
|
value = 62;
|
|
break;
|
|
default:
|
|
value = 80;
|
|
break;
|
|
}
|
|
|
|
series[series_code].enabled = true;
|
|
series[series_code].chart_data.push([
|
|
x,
|
|
value
|
|
]);
|
|
series[series_code].data[x] = value;
|
|
|
|
var duration = original_durations[series_code] !== undefined
|
|
? original_durations[series_code]
|
|
: runtime_thermostat[series_code];
|
|
|
|
durations[series_code][durations[series_code].length - 1].seconds += duration;
|
|
// durations[series_code][durations[series_code].length - 1].seconds += runtime_thermostat[series_code];
|
|
series[series_code].durations[x] = durations[series_code][durations[series_code].length - 1];
|
|
} else {
|
|
series[series_code].chart_data.push([
|
|
x,
|
|
null
|
|
]);
|
|
series[series_code].data[x] = null;
|
|
|
|
if (durations[series_code][durations[series_code].length - 1].seconds > 0) {
|
|
durations[series_code].push({'seconds': 0});
|
|
}
|
|
}
|
|
});
|
|
|
|
/*
|
|
* This is the ecobee code.
|
|
*
|
|
* var normalizedString = eventString;
|
|
* var vacationPattern = /(\S\S\S\s\d+\s\d\d\d\d)|(\d{12})/i;
|
|
* var smartRecoveryPattern = /smartRecovery/i;
|
|
* var smartAwayPattern = /smartAway/i;
|
|
* var smartHomePattern = /smartHome/i;
|
|
* var quickSavePattern = /quickSave/i;
|
|
*
|
|
* if (typeof eventString === 'string') {
|
|
* eventString = eventString.toLowerCase();
|
|
* normalizedString = eventString;
|
|
*
|
|
* if (eventString === 'auto' || eventString === 'today' || eventString === 'hold' || typeof thermostatClimates.climates[eventString] !== 'undefined') {
|
|
* normalizedString = 'hold';
|
|
* } else if (vacationPattern.test(eventString) || eventString.toLowerCase().indexOf('vacation') === 0) {
|
|
* normalizedString = 'vacation';
|
|
* } else if(smartRecoveryPattern.test(eventString)) {
|
|
* normalizedString = 'smartRecovery';
|
|
* } else if(smartHomePattern.test(eventString)) {
|
|
* normalizedString = 'smartHome';
|
|
* } else if(smartAwayPattern.test(eventString)) {
|
|
* normalizedString = 'smartAway';
|
|
* } else if(quickSavePattern.test(eventString)) {
|
|
* normalizedString = 'quickSave';
|
|
* } else {
|
|
* normalizedString = 'customEvent';
|
|
* }
|
|
* }
|
|
*/
|
|
|
|
/*
|
|
* Here are some examples of what I get in the database and what they map to
|
|
*
|
|
* calendar_event_home home
|
|
* calendar_event_away away
|
|
* calendar_event_smartrecovery (SmartRecovery)
|
|
* calendar_event_smartrecovery smartAway(SmartRecovery)
|
|
* calendar_event_smartrecovery auto(SmartRecovery)
|
|
* calendar_event_smartrecovery hold(SmartRecovery)
|
|
* calendar_event_smartrecovery 149831444185(SmartRecovery)
|
|
* calendar_event_smartrecovery Vacation(SmartRecovery)
|
|
* calendar_event_smartrecovery 152304757299(SmartRecovery)
|
|
* calendar_event_smartrecovery Apr 29 2016(SmartRecovery)
|
|
* calendar_event_smarthome smartHome
|
|
* calendar_event_smartaway smartAway
|
|
* calendar_event_hold hold
|
|
* calendar_event_vacation Vacation
|
|
* calendar_event_quicksave QuickSave
|
|
* calendar_event_vacation 151282889098
|
|
* calendar_event_vacation May 14 2016
|
|
* calendar_event_hold auto
|
|
* calendar_event_other NULL
|
|
* calendar_event_other HKhold
|
|
* calendar_event_other 8915FC00B0DA
|
|
* calendar_event_other 769347151
|
|
*/
|
|
|
|
/*
|
|
* Thanks, ecobee...I more or less copied this code from the ecobee Follow
|
|
* Me graph to make sure it's as accurate as possible.
|
|
*/
|
|
var this_calendar_event;
|
|
|
|
/*
|
|
* Display a fixed schedule in demo mode.
|
|
*/
|
|
if (window.is_demo === true) {
|
|
var m = moment(runtime_thermostat.timestamp);
|
|
|
|
// Moment and ecobee use different indexes for the days of the week
|
|
var day_of_week_index = (m.day() + 6) % 7;
|
|
|
|
// Ecobee splits the schedule up into 30 minute chunks; find the right one
|
|
var m_midnight = m.clone().startOf('day');
|
|
var minute_of_day = m.diff(m_midnight, 'minutes');
|
|
var chunk_of_day_index = Math.floor(minute_of_day / 30); // max 47
|
|
|
|
var ecobee_thermostat = beestat.cache.ecobee_thermostat[
|
|
thermostat.ecobee_thermostat_id
|
|
];
|
|
|
|
this_calendar_event = 'calendar_event_' + ecobee_thermostat.program.schedule[day_of_week_index][chunk_of_day_index];
|
|
} else {
|
|
if (runtime_thermostat.event === null) {
|
|
if (runtime_thermostat.climate === null) {
|
|
this_calendar_event = 'calendar_event_other';
|
|
} else {
|
|
this_calendar_event = 'calendar_event_' + runtime_thermostat.climate.toLowerCase();
|
|
}
|
|
} else if (runtime_thermostat.event.match(/SmartRecovery/i) !== null) {
|
|
this_calendar_event = 'calendar_event_smartrecovery';
|
|
} else if (runtime_thermostat.event.match(/^home$/i) !== null) {
|
|
this_calendar_event = 'calendar_event_home';
|
|
} else if (runtime_thermostat.event.match(/^away$/i) !== null) {
|
|
this_calendar_event = 'calendar_event_away';
|
|
} else if (runtime_thermostat.event.match(/^smarthome$/i) !== null) {
|
|
this_calendar_event = 'calendar_event_smarthome';
|
|
} else if (runtime_thermostat.event.match(/^smartaway$/i) !== null) {
|
|
this_calendar_event = 'calendar_event_smartaway';
|
|
} else if (runtime_thermostat.event.match(/^auto$/i) !== null) {
|
|
this_calendar_event = 'calendar_event_hold';
|
|
} else if (runtime_thermostat.event.match(/^today$/i) !== null) {
|
|
this_calendar_event = 'calendar_event_hold';
|
|
} else if (runtime_thermostat.event.match(/^hold$/i) !== null) {
|
|
this_calendar_event = 'calendar_event_hold';
|
|
} else if (runtime_thermostat.event.match(/^vacation$/i) !== null) {
|
|
this_calendar_event = 'calendar_event_vacation';
|
|
} else if (runtime_thermostat.event.match(/(\S\S\S\s\d+\s\d\d\d\d)|(\d{12})/i) !== null) {
|
|
this_calendar_event = 'calendar_event_vacation';
|
|
} else if (runtime_thermostat.event.match(/^quicksave$/i) !== null) {
|
|
this_calendar_event = 'calendar_event_quicksave';
|
|
} else {
|
|
this_calendar_event = 'calendar_event_other';
|
|
}
|
|
}
|
|
|
|
|
|
// Dynamically add new calendar events for custom climates.
|
|
if (
|
|
beestat.component.card.recent_activity.calendar_events.indexOf(this_calendar_event) === -1
|
|
) {
|
|
beestat.component.card.recent_activity.calendar_events.push(this_calendar_event);
|
|
|
|
series[this_calendar_event] = {
|
|
'enabled': false,
|
|
'chart_data': [],
|
|
'data': {},
|
|
'durations': {}
|
|
};
|
|
|
|
beestat.series[this_calendar_event] = {
|
|
'name': runtime_thermostat.climate,
|
|
'color': beestat.style.color.bluegreen.base
|
|
};
|
|
}
|
|
|
|
beestat.component.card.recent_activity.calendar_events.forEach(function(calendar_event) {
|
|
if (calendar_event === this_calendar_event && this_calendar_event !== 'calendar_event_other') {
|
|
var value = 95;
|
|
series[calendar_event].enabled = true;
|
|
series[calendar_event].chart_data.push([
|
|
x,
|
|
value
|
|
]);
|
|
series[calendar_event].data[x] = value;
|
|
} else {
|
|
series[calendar_event].chart_data.push([
|
|
x,
|
|
null
|
|
]);
|
|
series[calendar_event].data[x] = null;
|
|
}
|
|
});
|
|
|
|
/*
|
|
* HVAC Mode. This isn't graphed but it's available for the tooltip.
|
|
* series.system_mode.chart_data.push([x, runtime_thermostat.system_mode]);
|
|
*/
|
|
series.system_mode.data[x] = runtime_thermostat.system_mode;
|
|
|
|
// Setpoints
|
|
var setpoint_value_heat = beestat.temperature({'temperature': runtime_thermostat.setpoint_heat});
|
|
var setpoint_value_cool = beestat.temperature({'temperature': runtime_thermostat.setpoint_cool});
|
|
|
|
// NOTE: At one point I was also factoring in your heat/cool differential
|
|
// plus the extra degree offset ecobee adds when you are "away". That made
|
|
// the graph very exact but it wasn't really "setpoint" so I felt that would
|
|
// be confusing.
|
|
|
|
if (
|
|
runtime_thermostat.system_mode === 'auto' ||
|
|
runtime_thermostat.system_mode === 'heat' ||
|
|
runtime_thermostat.system_mode === 'auxiliary_heat' ||
|
|
runtime_thermostat.system_mode === null // Need this for the explicit null to remove from the graph.
|
|
) {
|
|
series.setpoint_heat.data[x] = setpoint_value_heat;
|
|
series.setpoint_heat.chart_data.push([
|
|
x,
|
|
setpoint_value_heat
|
|
]);
|
|
|
|
if (setpoint_value_heat !== null) {
|
|
self.y_min_ = Math.min(self.y_min_, setpoint_value_heat);
|
|
self.y_max_ = Math.max(self.y_max_, setpoint_value_heat);
|
|
}
|
|
} else {
|
|
|
|
/**
|
|
* Explicitly add a null entry to force an empty spot on the line.
|
|
* Otherwise Highcharts will connect gaps (see #119).
|
|
*/
|
|
series.setpoint_heat.data[x] = null;
|
|
series.setpoint_heat.chart_data.push([
|
|
x,
|
|
null
|
|
]);
|
|
}
|
|
|
|
if (
|
|
runtime_thermostat.system_mode === 'auto' ||
|
|
runtime_thermostat.system_mode === 'cool' ||
|
|
runtime_thermostat.system_mode === null // Need this for the explicit null to remove from the graph.
|
|
) {
|
|
series.setpoint_cool.data[x] = setpoint_value_cool;
|
|
series.setpoint_cool.chart_data.push([
|
|
x,
|
|
setpoint_value_cool
|
|
]);
|
|
|
|
if (setpoint_value_cool !== null) {
|
|
self.y_min_ = Math.min(self.y_min_, setpoint_value_cool);
|
|
self.y_max_ = Math.max(self.y_max_, setpoint_value_cool);
|
|
}
|
|
} else {
|
|
|
|
/**
|
|
* Explicitly add a null entry to force an empty spot on the line.
|
|
* Otherwise Highcharts will connect gaps (see #119).
|
|
*/
|
|
series.setpoint_cool.data[x] = null;
|
|
series.setpoint_cool.chart_data.push([
|
|
x,
|
|
null
|
|
]);
|
|
}
|
|
|
|
// Indoor temperature
|
|
var indoor_temperature_value = beestat.temperature(runtime_thermostat.indoor_temperature);
|
|
series.indoor_temperature.data[x] = indoor_temperature_value;
|
|
|
|
/*
|
|
* Draw a data point if:
|
|
* It's one of the nth data points (smoothing) OR
|
|
* The previous value is null (forces data point right when null data stops instead of on the 10th) OR
|
|
* The current value is null (forces null data to display as a blank section) PR
|
|
* The next value is null (forces data point right when null data starts instead of on the 10th)
|
|
* The current value is the last value (forces data point right at the end)
|
|
*/
|
|
if (
|
|
i % smoothing_factor === 0 ||
|
|
(
|
|
previous_indoor_temperature_value === null &&
|
|
indoor_temperature_value !== null
|
|
) ||
|
|
indoor_temperature_value === null ||
|
|
(
|
|
beestat.cache.runtime_thermostat[i + 1] !== undefined &&
|
|
beestat.cache.runtime_thermostat[i + 1].indoor_temperature === null
|
|
) ||
|
|
i === (beestat.cache.runtime_thermostat.length - 1)
|
|
) {
|
|
series.indoor_temperature.enabled = true;
|
|
series.indoor_temperature.chart_data.push([
|
|
x,
|
|
indoor_temperature_value
|
|
]);
|
|
|
|
if (indoor_temperature_value !== null) {
|
|
self.y_min_ = Math.min(self.y_min_, indoor_temperature_value);
|
|
self.y_max_ = Math.max(self.y_max_, indoor_temperature_value);
|
|
}
|
|
}
|
|
|
|
// Outdoor temperature
|
|
var outdoor_temperature_value = beestat.temperature(runtime_thermostat.outdoor_temperature);
|
|
series.outdoor_temperature.data[x] = outdoor_temperature_value;
|
|
|
|
/*
|
|
* Draw a data point if:
|
|
* It's one of the 10th data points (smoothing) OR
|
|
* The previous value is null (forces data point right when null data stops instead of on the 10th) OR
|
|
* The current value is null (forces null data to display as a blank section) PR
|
|
* The next value is null (forces data point right when null data starts instead of on the 10th)
|
|
* The current value is the last value (forces data point right at the end)
|
|
*/
|
|
if (
|
|
i % smoothing_factor === 0 ||
|
|
(
|
|
previous_outdoor_temperature_value === null &&
|
|
outdoor_temperature_value !== null
|
|
) ||
|
|
outdoor_temperature_value === null ||
|
|
(
|
|
beestat.cache.runtime_thermostat[i + 1] !== undefined &&
|
|
beestat.cache.runtime_thermostat[i + 1].outdoor_temperature === null
|
|
) ||
|
|
i === (beestat.cache.runtime_thermostat.length - 1)
|
|
) {
|
|
series.outdoor_temperature.enabled = true;
|
|
series.outdoor_temperature.chart_data.push([
|
|
x,
|
|
outdoor_temperature_value
|
|
]);
|
|
|
|
if (outdoor_temperature_value !== null) {
|
|
self.y_min_ = Math.min(self.y_min_, outdoor_temperature_value);
|
|
self.y_max_ = Math.max(self.y_max_, outdoor_temperature_value);
|
|
}
|
|
}
|
|
|
|
// Indoor humidity
|
|
var indoor_humidity_value;
|
|
if (runtime_thermostat.indoor_humidity !== null) {
|
|
indoor_humidity_value = parseInt(
|
|
runtime_thermostat.indoor_humidity,
|
|
10
|
|
);
|
|
} else {
|
|
indoor_humidity_value = null;
|
|
}
|
|
series.indoor_humidity.data[x] = indoor_humidity_value;
|
|
|
|
/*
|
|
* Draw a data point if:
|
|
* It's one of the 10th data points (smoothing) OR
|
|
* The previous value is null (forces data point right when null data stops instead of on the 10th) OR
|
|
* The current value is null (forces null data to display as a blank section) PR
|
|
* The next value is null (forces data point right when null data starts instead of on the 10th)
|
|
* The current value is the last value (forces data point right at the end)
|
|
*/
|
|
if (
|
|
i % smoothing_factor === 0 ||
|
|
(
|
|
previous_indoor_humidity_value === null &&
|
|
indoor_humidity_value !== null
|
|
) ||
|
|
indoor_humidity_value === null ||
|
|
(
|
|
beestat.cache.runtime_thermostat[i + 1] !== undefined &&
|
|
beestat.cache.runtime_thermostat[i + 1].indoor_humidity === null
|
|
) ||
|
|
i === (beestat.cache.runtime_thermostat.length - 1)
|
|
) {
|
|
series.indoor_humidity.enabled = true;
|
|
series.indoor_humidity.chart_data.push([
|
|
x,
|
|
indoor_humidity_value
|
|
]);
|
|
}
|
|
|
|
// Outdoor humidity
|
|
var outdoor_humidity_value;
|
|
if (runtime_thermostat.outdoor_humidity !== null) {
|
|
outdoor_humidity_value = parseInt(
|
|
runtime_thermostat.outdoor_humidity,
|
|
10
|
|
);
|
|
} else {
|
|
outdoor_humidity_value = null;
|
|
}
|
|
series.outdoor_humidity.data[x] = outdoor_humidity_value;
|
|
|
|
/*
|
|
* Draw a data point if:
|
|
* It's one of the 10th data points (smoothing) OR
|
|
* The previous value is null (forces data point right when null data stops instead of on the 10th) OR
|
|
* The current value is null (forces null data to display as a blank section) PR
|
|
* The next value is null (forces data point right when null data starts instead of on the 10th)
|
|
* The current value is the last value (forces data point right at the end)
|
|
*/
|
|
if (
|
|
i % smoothing_factor === 0 ||
|
|
(
|
|
previous_outdoor_humidity_value === null &&
|
|
outdoor_humidity_value !== null
|
|
) ||
|
|
outdoor_humidity_value === null ||
|
|
(
|
|
beestat.cache.runtime_thermostat[i + 1] !== undefined &&
|
|
beestat.cache.runtime_thermostat[i + 1].outdoor_humidity === null
|
|
) ||
|
|
i === (beestat.cache.runtime_thermostat.length - 1)
|
|
) {
|
|
series.outdoor_humidity.enabled = true;
|
|
series.outdoor_humidity.chart_data.push([
|
|
x,
|
|
outdoor_humidity_value
|
|
]);
|
|
}
|
|
|
|
previous_indoor_temperature_value = indoor_temperature_value;
|
|
previous_outdoor_temperature_value = outdoor_temperature_value;
|
|
previous_indoor_humidity_value = indoor_humidity_value;
|
|
previous_outdoor_humidity_value = outdoor_humidity_value;
|
|
});
|
|
|
|
return series;
|
|
};
|
|
|
|
/**
|
|
* Get the title of the card.
|
|
*
|
|
* @return {string} Title
|
|
*/
|
|
beestat.component.card.recent_activity.prototype.get_title_ = function() {
|
|
return 'Recent Activity';
|
|
};
|
|
|
|
/**
|
|
* Get the subtitle of the card.
|
|
*
|
|
* @return {string} Subtitle
|
|
*/
|
|
beestat.component.card.recent_activity.prototype.get_subtitle_ = function() {
|
|
var s = (beestat.setting('recent_activity_time_count') > 1) ? 's' : '';
|
|
|
|
return 'Past ' +
|
|
beestat.setting('recent_activity_time_count') +
|
|
' ' +
|
|
beestat.setting('recent_activity_time_period') +
|
|
s;
|
|
};
|
|
|
|
/**
|
|
* Determine whether or not enough data is currently available to render this
|
|
* card. In this particular case require data from 7 days to an hour ago to be synced.
|
|
*
|
|
* @return {boolean} Whether or not the data is available.
|
|
*/
|
|
beestat.component.card.recent_activity.prototype.data_available_ = function() {
|
|
// Demo can juse grab whatever data is there.
|
|
if (window.is_demo === true) {
|
|
return true;
|
|
}
|
|
|
|
var thermostat = beestat.cache.thermostat[beestat.setting('thermostat_id')];
|
|
|
|
var current_sync_begin = moment.utc(thermostat.sync_begin);
|
|
var current_sync_end = moment.utc(thermostat.sync_end);
|
|
|
|
var required_sync_begin = moment().subtract(7, 'day');
|
|
required_sync_begin = moment.max(
|
|
required_sync_begin,
|
|
moment(thermostat.first_connected)
|
|
);
|
|
var required_sync_end = moment().subtract(1, 'hour');
|
|
|
|
return (
|
|
current_sync_begin.isSameOrBefore(required_sync_begin) &&
|
|
current_sync_end.isSameOrAfter(required_sync_end)
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Get the data needed to render this card.
|
|
*/
|
|
beestat.component.card.recent_activity.prototype.get_data_ = function() {
|
|
var self = this;
|
|
var thermostat = beestat.cache.thermostat[beestat.setting('thermostat_id')];
|
|
|
|
new beestat.api()
|
|
.add_call(
|
|
'runtime_thermostat',
|
|
'read',
|
|
{
|
|
'attributes': {
|
|
'thermostat_id': thermostat.thermostat_id,
|
|
'timestamp': {
|
|
'value': moment()
|
|
.subtract(7, 'd')
|
|
.format('YYYY-MM-DD'),
|
|
'operator': '>'
|
|
}
|
|
}
|
|
}
|
|
)
|
|
.set_callback(function(response) {
|
|
beestat.cache.set('runtime_thermostat', response);
|
|
self.rerender();
|
|
})
|
|
.send();
|
|
};
|