mirror of
https://github.com/beestat/app.git
synced 2025-05-24 02:14:03 -04:00
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.json_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();
|
|
};
|