1
0
mirror of https://github.com/beestat/app.git synced 2025-05-24 02:14:03 -04:00

Time to improvements

This commit is contained in:
Jon Ziebell 2024-10-26 05:10:35 -04:00
parent 7a26405dea
commit 85ebe47737
9 changed files with 768 additions and 20 deletions

View File

@ -255,6 +255,9 @@ class profile extends cora\crud {
$degree_days_base_temperature = 65;
$degree_days = [];
$begin_runtime = [];
$extreme_times = [
'high' => []
];
while($current_timestamp <= $end_timestamp) {
// Get a new chunk of data.
@ -290,19 +293,81 @@ class profile extends cora\crud {
// consistently represented instead of having to do this logic
// throughout the generator.
$runtime = [];
$degree_days_date = date('Y-m-d', $current_timestamp);
$degree_days_temperatures = [];
// $local_datetime = get_local_datetime(
// date('Y-m-d H:i:s', $current_timestamp),
// $thermostat['time_zone']
// );
// $process_date = date('Y-m-d', $current_timestamp);
$process_date = get_local_datetime(
date('Y-m-d H:i:s', $current_timestamp),
$thermostat['time_zone'],
'Y-m-d'
);
$process_date_temperatures = [];
// $extreme_times_date = date('Y-m-d', $current_timestamp);
while($row = $result->fetch_assoc()) {
$timestamp = strtotime($row['timestamp']);
$date = date('Y-m-d', $timestamp);
// $date = date('Y-m-d', $timestamp);
$date = get_local_datetime(
date('Y-m-d H:i:s', $timestamp),
$thermostat['time_zone'],
'Y-m-d'
);
// Degree days
if($date !== $degree_days_date) {
$degree_days[] = (array_mean($degree_days_temperatures) / 10) - $degree_days_base_temperature;
$degree_days_date = $date;
$degree_days_temperatures = [];
if($date !== $process_date) {
// Degree days
$degree_days[] = (array_mean(array_column($process_date_temperatures, 'outdoor_temperature')) / 10) - $degree_days_base_temperature;
// Max temp
$extreme_times_width = 10;
// Sort the temperatures in descending order and pick the top
usort($process_date_temperatures, function($a, $b) {
return $b['outdoor_temperature'] <=> $a['outdoor_temperature'];
});
$highest_temperatures = array_slice($process_date_temperatures, 0, $extreme_times_width);
// print_r($highest_temperatures);
// Calculate the average timestamp
$average_timestamp = array_sum(array_column($highest_temperatures, 'timestamp')) / count($highest_temperatures);
// Save the average timestamp to $extreme_times using the day number of the year
$extreme_times['high'][date('z', $timestamp)] = date('H:i', $average_timestamp);
$process_date = $date;
$process_date_temperatures = [];
}
$degree_days_temperatures[] = $row['outdoor_temperature'];
$process_date_temperatures[] = [
'outdoor_temperature' => $row['outdoor_temperature'],
'timestamp' => $timestamp,
'tmp_timestamp' => date('c', $timestamp)
];
// Extreme times
// if($date !== $extreme_times_date) {
// $degree_days[] = (array_mean($process_date_temperatures) / 10) - $degree_days_base_temperature;
// $extreme_times_date = $date;
// $process_date_temperatures = [];
// }
// $extreme_times_temperatures[] = $row['outdoor_temperature'];
/**
* Store an extreme_date just like degree days date (or probably
* just use the same value), then put all of the outdoor weather
* data into an array. When the date changes, process the data. Note
* that I should be storing the past 7 days of data in this array.
*
* Processing the data should find the top X values and then
* calculate the average date of them (not midpoint). Do that for
* the past 7 days and average those together, then remove one day
* of results from the array.
*/
if($first_timestamp === null) {
$first_timestamp = $row['timestamp'];
@ -969,6 +1034,9 @@ class profile extends cora\crud {
'latitude' => $thermostat_database['address_latitude'],
'longitude' => $thermostat_database['address_longitude']
],
'extreme_times' => [
'high' => $extreme_times['high']
],
'metadata' => [
'generated_at' => date('c'),
'duration' => $first_timestamp === null ? null : round((time() - strtotime($first_timestamp)) / 86400),

View File

@ -16,7 +16,7 @@ class user extends cora\crud {
'sync_patreon_status',
'unlink_patreon_account',
],
'public' => []
'public' => ['force_log_in']
];
/**

View File

@ -0,0 +1,267 @@
beestat.time_to_detail = {};
/**
* Get a bunch of data for the current time_to_detail rows. Includes
* basically everything you need to make a cool chart.
*
* @param {number} thermostat_id The thermostat_id to get data for.
*
* @return {object} The data.
*/
beestat.time_to_detail.get_data = function(thermostat_id) {
const thermostat = beestat.cache.thermostat[thermostat_id];
const operating_mode = beestat.thermostat.get_operating_mode(
thermostat.thermostat_id
);
const linear_trendline = thermostat.profile.temperature[operating_mode].linear_trendline;
// Convert "heat_1" etc to "heat"
const simplified_operating_mode = operating_mode.replace(/[_\d]|auxiliary/g, '');
var data = {
'x': [],
'series': {},
'metadata': {
'series': {},
'chart': {
'setpoint_reached_m': null
}
}
};
// Initialize a bunch of stuff.
[
'outdoor_temperature',
'indoor_temperature',
'indoor_cool_1_delta',
'indoor_cool_2_delta',
'indoor_heat_1_delta',
'indoor_heat_2_delta',
'indoor_auxiliary_heat_1_delta',
'indoor_auxiliary_heat_2_delta',
'setpoint_heat',
'setpoint_cool'
].forEach(function(series_code) {
data.series[series_code] = [];
data.metadata.series[series_code] = {
'active': false,
'data': {}
};
if (beestat.series[series_code] !== undefined) {
data.metadata.series[series_code].name = beestat.series[series_code].name;
} else {
data.metadata.series[series_code].name = null;
}
});
// Initialize a bunch of stuff.
data.metadata.series.outdoor_temperature.active = true;
data.metadata.series.indoor_temperature.active = true;
// [
// 'outdoor_temperature',
// 'indoor_temperature'
// ].forEach(function(series_code) {
// data.metadata.series[series_code].active = true;
// });
begin_m = moment();
end_m = moment().add(12, 'hour');
// Loop.
let current_m = begin_m.clone();
let current_indoor_temperature = thermostat.temperature;
let current_outdoor_temperature;
// let current_indoor_cool_1_delta;
let current_setpoint_heat;
let current_setpoint_cool;
while (
current_m.isSameOrAfter(end_m) === false
) {
data.x.push(current_m.clone());
current_outdoor_temperature = beestat.time_to_detail.predict_outdoor_temperature(
thermostat_id,
// thermostat.weather.temperature,
// begin_m,
current_m
);
current_indoor_temperature = beestat.time_to_detail.predict_indoor_temperature(
thermostat_id,
current_indoor_temperature,
current_outdoor_temperature,
current_m.clone().subtract(1, 'minute'),
current_m
);
// current_indoor_temperature = prediction.indoor_temperature;
// current_degrees_per_hour = prediction.degrees_per_hour;
data.series.outdoor_temperature.push(current_outdoor_temperature);
data.metadata.series.outdoor_temperature.data[current_m.valueOf()] = current_outdoor_temperature;
data.series.indoor_temperature.push(current_indoor_temperature);
data.metadata.series.indoor_temperature.data[current_m.valueOf()] = current_indoor_temperature;
const setpoint = beestat.time_to_detail.get_setpoint(thermostat_id, current_m);
[
'cool',
'heat',
].forEach(function(this_operating_mode) {
if(this_operating_mode === simplified_operating_mode) {
data.metadata.series[`setpoint_${this_operating_mode}`].active = true;
data.series[`setpoint_${this_operating_mode}`].push(setpoint[this_operating_mode]);
data.metadata.series[`setpoint_${this_operating_mode}`].data[current_m.valueOf()] = setpoint[this_operating_mode];
}
});
[
'cool_1',
'cool_2',
'heat_1',
'heat_2',
'auxiliary_heat_1',
'auxiliary_heat_2'
].forEach(function(operating_mode) {
if(
operating_mode.includes(simplified_operating_mode) === true &&
thermostat.profile.temperature[operating_mode] !== null
) {
const linear_trendline = thermostat.profile.temperature[operating_mode].linear_trendline;
const degrees_per_hour = (linear_trendline.slope * current_outdoor_temperature) + linear_trendline.intercept;
data.metadata.series[`indoor_${operating_mode}_delta`].active = true;
data.series[`indoor_${operating_mode}_delta`].push(degrees_per_hour);
data.metadata.series[`indoor_${operating_mode}_delta`].data[current_m.valueOf()] = degrees_per_hour;
}
});
if (
current_indoor_temperature <= setpoint[simplified_operating_mode] &&
data.metadata.chart.setpoint_reached_m === null
) {
data.metadata.chart.setpoint_reached_m = current_m.clone();
// Redefine the end to go 25% further than we have already.
end_m = begin_m.clone().add((current_m.diff(begin_m, 'minutes') * 1.25), 'minutes');
}
current_m.add(1, 'minute');
}
return data;
};
/**
* Predict outdoor temperature using a simple sine wave.
*
* @param {number} thermostat_id The thermostat_id
* @param {moment} current_m Timestamp to predict for
*
* @return {number} Predicted outdoor temperature
*/
beestat.time_to_detail.predict_outdoor_temperature = function(thermostat_id, current_m) {
const thermostat = beestat.cache.thermostat[thermostat_id];
const t = (current_m.hours() * 60) + current_m.minutes();
// Period and frequency constants; one day (in minutes)
const period = 1440;
const frequency = (2 * Math.PI) / period;
// Determine the phase shift based on the warmest time of the day.
const desired_t_max = beestat.time_to_detail.get_extreme_high_time(thermostat_id, current_m);
const default_t_max = period / 4;
const phase_shift = desired_t_max - default_t_max;
// Determine the amplitude and y_offset based on the predicted high and low
// temps.
// TODO: the low could actually be wrong if the predicted low isn't actually the low of the entire 24h day
const temperature_high = thermostat.weather.temperature_high;
const temperature_low = thermostat.weather.temperature_low;
const amplitude = (temperature_high - temperature_low) / 2;
const y_offset = (temperature_high + temperature_low) / 2;
return amplitude * Math.sin(frequency * (t - phase_shift)) + y_offset;
}
beestat.time_to_detail.get_extreme_high_time = function(thermostat_id, current_m) {
const count = 30;
const thermostat = beestat.cache.thermostat[thermostat_id];
const day_of_year = current_m.dayOfYear(); // 1-indexed
let extreme_high_times = [];
// Function to get extreme high time for a given day of year
const get_extreme_high_time_for_day = function(day_of_year) {
return thermostat.profile.extreme_times.high[
((day_of_year - 1) + 365) % 365
];
};
// Check from -14 to +7 days
for (let i = (-count); i <= (count / 2); i++) {
const target_day_of_year = day_of_year + i;
const extreme_time = get_extreme_high_time_for_day(target_day_of_year);
if (extreme_time !== undefined) {
extreme_high_times.push(extreme_time);
}
}
// Take the last 15 values, or fewer if not enough
extreme_high_times = extreme_high_times.slice(-count);
// Convert to minutes
const extreme_high_minutes = extreme_high_times.map(function(extreme_high_time) {
const extreme_high_time_m = moment(extreme_high_time, 'HH:mm');
return extreme_high_time_m.hours() * 60 + extreme_high_time_m.minutes();
});
// TODO: Remove outliers here if desired
const average_extreme_high_minutes = extreme_high_minutes.reduce(
function(sum, value) {
return (sum + value);
},
0
) / extreme_high_times.length;
return average_extreme_high_minutes;
};
beestat.time_to_detail.predict_indoor_temperature = function(thermostat_id, begin_indoor_temperature, outdoor_temperature, begin_m, current_m) {
const thermostat = beestat.cache.thermostat[thermostat_id];
// const operating_mode = beestat.thermostat.get_operating_mode(
// thermostat.thermostat_id
// );
const operating_mode = 'cool_1';
const linear_trendline = thermostat.profile.temperature[operating_mode].linear_trendline;
const degrees_per_hour = (linear_trendline.slope * outdoor_temperature) + linear_trendline.intercept;
const degrees_per_minute = degrees_per_hour / 60;
// const prediction = {
// 'indoor_temperature': begin_indoor_temperature + (degrees_per_minute * current_m.diff(begin_m, 'minutes'))
// };
// prediction[`indoor_${operating_mode}_delta`] = degrees_per_hour;
return begin_indoor_temperature + (degrees_per_minute * current_m.diff(begin_m, 'minutes'));
}
beestat.time_to_detail.get_setpoint = function(thermostat_id, current_m) {
const thermostat = beestat.cache.thermostat[thermostat_id];
const climates_by_climate_ref = [];
thermostat.program.climates.forEach(function(climate) {
climates_by_climate_ref[climate.climateRef] = climate;
})
const ecobee_day = current_m.day();
const ecobee_half_hour = Math.floor((current_m.hour() * 60 + current_m.minute()) / 30);
const schedule = thermostat.program.schedule[ecobee_day][ecobee_half_hour];
return {
'heat': climates_by_climate_ref[schedule].heatTemp,
'cool': climates_by_climate_ref[schedule].coolTemp
};
}

View File

@ -346,7 +346,6 @@ beestat.component.card.system.prototype.decorate_time_to_temperature_ = function
const container = $.createElement('div').style({
'background': beestat.style.color.bluegray.dark,
'padding': beestat.style.size.gutter / 2,
'text-align': 'center',
'margin-top': beestat.style.size.gutter,
'border-radius': beestat.style.size.border_radius
});
@ -365,13 +364,13 @@ beestat.component.card.system.prototype.decorate_time_to_temperature_ = function
const outdoor_temperature = thermostat.weather.temperature;
const degrees_per_hour = (linear_trendline.slope * outdoor_temperature) + linear_trendline.intercept;
header_text += ' (' +
beestat.temperature({
'temperature': degrees_per_hour,
'delta': true,
'units': true
}) +
' / h)';
// header_text += ' (' +
// beestat.temperature({
// 'temperature': degrees_per_hour,
// 'delta': true,
// 'units': true
// }) +
// ' / h)';
if (
(
@ -417,12 +416,55 @@ beestat.component.card.system.prototype.decorate_time_to_temperature_ = function
}
}
container.appendChild(
const grid = $.createElement('div')
.style({
'display': 'grid',
'grid-template-columns': '3fr 1fr', // 75% for left, 25% for right
'grid-gap': `${beestat.style.size.gutter}px`,
'align-items': 'center', // Center content vertically
});
const left = $.createElement('div');
// .style({
// 'overflow-wrap': 'break-word', // Allow wrapping if the content doesn't fit
// 'word-wrap': 'break-word',
// 'word-break': 'break-word',
// });
left.appendChild(
$.createElement('div')
.style('font-weight', 'bold')
.innerText(header_text)
);
container.appendChild($.createElement('div').innerText(text));
left.appendChild(
$.createElement('div')
.innerText(text)
);
const right = $.createElement('div')
.style({
'text-align': 'right', // Right-align the content of the right column
});
var cancel = new beestat.component.tile()
.set_icon('chart_line')
.set_shadow(false)
.set_background_hover_color('#fff')
.set_text_hover_color(beestat.style.color.bluegray.dark)
.set_text('Detail')
.addEventListener('click', function () {
(new beestat.component.modal.time_to_detail()).render();
})
.render(right);
grid.appendChild(left);
grid.appendChild(right);
container.appendChild(grid);
// setTimeout(function() {
// (new beestat.component.modal.time_to_detail()).render();
// }, 0);
};
/**

View File

@ -511,6 +511,7 @@ beestat.component.card.three_d.prototype.decorate_controls_ = function(parent) {
address.normalized.metadata.latitude,
address.normalized.metadata.longitude
);
console.info(times);
const sunrise_m = moment(times.sunrise);
const sunrise_percentage = ((sunrise_m.hours() * 60) + sunrise_m.minutes()) / 1440 * 100;

View File

@ -211,6 +211,7 @@ beestat.component.chart.prototype.get_options_chart_ = function() {
},
'spacing': this.get_options_chart_spacing_(),
// For consistent left spacing on charts with no y-axis values
'marginTop': this.get_options_chart_marginTop_(),
'marginLeft': this.get_options_chart_marginLeft_(),
'marginRight': this.get_options_chart_marginRight_(),
'marginBottom': this.get_options_chart_marginBottom_(),
@ -230,6 +231,15 @@ beestat.component.chart.prototype.get_options_chart_ = function() {
};
};
/**
* Get the top margin for the chart.
*
* @return {number} The top margin for the chart.
*/
beestat.component.chart.prototype.get_options_chart_marginTop_ = function() {
return undefined;
};
/**
* Get the left margin for the chart.
*

View File

@ -0,0 +1,313 @@
/**
* Runtime thermostat detail temperature chart.
*
* @param {object} data The chart data.
*/
beestat.component.chart.time_to_detail = function(data) {
this.data_ = data;
beestat.component.chart.apply(this, arguments);
};
beestat.extend(beestat.component.chart.time_to_detail, beestat.component.chart);
/**
* Override for get_options_xAxis_.
*
* @return {object} The xAxis options.
*/
beestat.component.chart.time_to_detail.prototype.get_options_xAxis_ = function() {
return {
'categories': this.data_.x,
'lineColor': beestat.style.color.bluegray.light,
'tickLength': 0,
'labels': {
'style': {
'color': beestat.style.color.gray.base,
'font-size': '12px'
},
'formatter': this.get_options_xAxis_labels_formatter_()
},
'crosshair': this.get_options_xAxis_crosshair_(),
'plotLines': [
{
'color': beestat.series.outdoor_temperature.color,
'dashStyle': 'ShortDash',
'width': 1,
'label': {
'style': {
'color': beestat.series.outdoor_temperature.color
},
'useHTML': true,
'text': beestat.time(
this.data_.metadata.chart.setpoint_reached_m.diff(moment(), 'second'),
) + ' (' + this.data_.metadata.chart.setpoint_reached_m.format('h:mm a') + ')'
},
'value': this.data_.metadata.chart.setpoint_reached_m.diff(moment(), 'minute'),
'zIndex': 2
}
]
};
};
/**
* Override for get_options_xAxis_labels_formatter_.
*
* @return {Function} xAxis labels formatter.
*/
beestat.component.chart.time_to_detail.prototype.get_options_xAxis_labels_formatter_ = function() {
var current_day;
var current_time;
return function() {
var day = this.value.format('ddd');
var time = this.value.clone();
var minutes = time.minutes();
var rounded_minutes = Math.round(minutes / 5) * 5;
time.minutes(rounded_minutes).seconds(0);
time = time.format('h:mm');
var label_parts = [];
if (day !== current_day) {
label_parts.push(day);
}
if (time !== current_time) {
label_parts.push(time);
}
current_day = day;
current_time = time;
return label_parts.join(' ');
};
};
/**
* Override for get_options_series_.
*
* @return {Array} All of the series to display on the chart.
*/
beestat.component.chart.time_to_detail.prototype.get_options_series_ = function() {
var self = this;
var series = [];
// Indoor/Outdoor Temperature
[
'indoor_temperature',
'outdoor_temperature',
].forEach(function(series_code) {
if (self.data_.metadata.series[series_code].active === true) {
series.push({
'name': series_code,
'data': self.data_.series[series_code],
'color': beestat.series[series_code].color,
'yAxis': (series_code === 'indoor_temperature') ? 0 : 1,
'type': 'spline',
'dashStyle': (series_code === 'indoor_temperature') ? 'Solid' : 'ShortDash',
'lineWidth': (series_code === 'indoor_temperature') ? 2 : 1
});
}
});
// Setpoint Heat/Cool
[
'setpoint_heat',
'setpoint_cool'
].forEach(function(series_code) {
if (self.data_.metadata.series[series_code].active === true) {
series.push({
'name': series_code,
'data': self.data_.series[series_code],
'color': beestat.series[series_code].color,
'yAxis': 0,
'type': 'line',
'lineWidth': 1,
'step': 'right',
'className': 'crisp_edges'
});
}
});
return series;
};
/**
* Override for get_options_yAxis_.
*
* @return {Array} The y-axis options.
*/
beestat.component.chart.time_to_detail.prototype.get_options_yAxis_ = function() {
return [
// Indoor Temperature
{
'gridLineColor': beestat.style.color.bluegray.light,
'gridLineDashStyle': 'longdash',
'allowDecimals': false,
'title': {'text': null},
'labels': {
'style': {
'color': beestat.style.color.gray.base,
'fontSize': '11px'
},
'formatter': function() {
return this.value + beestat.setting('units.temperature');
}
}
},
// Outdoor Temperature
{
'gridLineColor': beestat.style.color.bluegray.light,
'opposite': true,
'gridLineDashStyle': 'longdash',
'allowDecimals': false,
'title': {'text': null},
'labels': {
'style': {
'color': beestat.style.color.gray.base,
'fontSize': '11px'
},
'formatter': function() {
return this.value + beestat.setting('units.temperature');
}
}
}
];
};
/**
* Override for get_options_tooltip_formatter_.
*
* @return {Function} The tooltip formatter.
*/
beestat.component.chart.time_to_detail.prototype.get_options_tooltip_formatter_ = function() {
var self = this;
return function() {
var points = [];
var x = this.x;
var sections = [];
var groups = {
'data': [],
'delta': []
};
// Add some other stuff.
[
'indoor_temperature',
'outdoor_temperature',
'setpoint_heat',
'setpoint_cool',
'indoor_cool_1_delta',
'indoor_cool_2_delta',
'indoor_heat_1_delta',
'indoor_heat_2_delta',
'indoor_auxiliary_heat_1_delta',
// 'indoor_auxiliary_heat_2_delta'
].forEach(function(series_code) {
if (
self.data_.metadata.series[series_code].active === true
) {
points.push({
'series_code': series_code,
'value': self.data_.metadata.series[series_code].data[x.valueOf()],
'color': beestat.series[series_code].color
});
}
});
points.forEach(function(point) {
var label;
var value;
var color;
var group;
if (
point.series_code.includes('temperature') === true ||
point.series_code.includes('setpoint') === true
) {
group = 'data';
label = beestat.series[point.series_code].name;
color = beestat.series[point.series_code].color;
value = point.value;
value = beestat.temperature({
'temperature': value,
'input_temperature_unit': beestat.setting('units.temperature'),
'units': true
});
} else if(point.series_code.includes('delta') === true) {
group = 'delta';
label = beestat.series[point.series_code].name;
color = beestat.series[point.series_code].color;
value = point.value;
value = beestat.temperature({
'temperature': point.value,
'units': true,
'input_temperature_unit': beestat.setting('units.temperature'),
'delta': true,
'type': 'string'
}) + ' / h';
if (point.value.toFixed(1) > 0) {
value = '+' + value;
}
}
else {
return;
}
groups[group].push({
'label': label,
'value': value,
'color': color
});
});
sections.push(groups.data);
sections.push(groups.delta);
var title = this.x.format('ddd, MMM D @ h:mma');
return self.tooltip_formatter_helper_(
title,
sections
);
};
};
/**
* Get the height of the chart.
*
* @return {number} The height of the chart.
*/
beestat.component.chart.time_to_detail.prototype.get_options_chart_height_ = function() {
return 350;
};
/**
* Get the top margin for the chart.
*
* @return {number} The top margin for the chart.
*/
beestat.component.chart.time_to_detail.prototype.get_options_chart_marginTop_ = function() {
return 20;
};
/**
* Get the left margin for the chart.
*
* @return {number} The left margin for the chart.
*/
beestat.component.chart.time_to_detail.prototype.get_options_chart_marginLeft_ = function() {
return 45;
};
/**
* Get the right margin for the chart.
*
* @return {number} The right margin for the chart.
*/
beestat.component.chart.time_to_detail.prototype.get_options_chart_marginRight_ = function() {
return 45;
};

View File

@ -0,0 +1,44 @@
/**
* Current time_to_detail.
*/
beestat.component.modal.time_to_detail = function() {
var self = this;
beestat.dispatcher.addEventListener(
'cache.thermostat',
function() {
self.rerender();
}
);
beestat.component.modal.apply(this, arguments);
};
beestat.extend(beestat.component.modal.time_to_detail, beestat.component.modal);
/**
* Decorate
*
* @param {rocket.Elements} parent
*/
beestat.component.modal.time_to_detail.prototype.decorate_contents_ = function(parent) {
new beestat.component.chart.time_to_detail(
beestat.time_to_detail.get_data(beestat.setting('thermostat_id'))
).render(parent);
};
/**
* Get the title of the chart.
*
* @return {string}
*/
beestat.component.modal.time_to_detail.prototype.get_title_ = function() {
const thermostat = beestat.cache.thermostat[beestat.setting('thermostat_id')];
const operating_mode = beestat.thermostat.get_operating_mode(
thermostat.thermostat_id
);
// Convert "heat_1" etc to "heat"
const simplified_operating_mode = operating_mode.replace(/[_\d]|auxiliary/g, '');
return 'Time to ' + simplified_operating_mode;
};

View File

@ -56,6 +56,7 @@ if($setting->get('environment') === 'dev' || $setting->get('environment') === 'd
echo '<script src="/js/beestat/math.js"></script>' . PHP_EOL;
echo '<script src="/js/beestat/platform.js"></script>' . PHP_EOL;
echo '<script src="/js/beestat/text_dimensions.js"></script>' . PHP_EOL;
echo '<script src="/js/beestat/time_to_detail.js"></script>' . PHP_EOL;
// Layer
echo '<script src="/js/layer.js"></script>' . PHP_EOL;
@ -112,6 +113,7 @@ if($setting->get('environment') === 'dev' || $setting->get('environment') === 'd
echo '<script src="/js/component/chart/voc_concentration.js"></script>' . PHP_EOL;
echo '<script src="/js/component/chart/co2_concentration.js"></script>' . PHP_EOL;
echo '<script src="/js/component/chart/air_quality.js"></script>' . PHP_EOL;
echo '<script src="/js/component/chart/time_to_detail.js"></script>' . PHP_EOL;
echo '<script src="/js/component/header.js"></script>' . PHP_EOL;
echo '<script src="/js/component/icon.js"></script>' . PHP_EOL;
echo '<script src="/js/component/layout.js"></script>' . PHP_EOL;
@ -144,6 +146,7 @@ if($setting->get('environment') === 'dev' || $setting->get('environment') === 'd
echo '<script src="/js/component/modal/floor_plan_elevation_help.js"></script>' . PHP_EOL;
echo '<script src="/js/component/modal/visualize_custom.js"></script>' . PHP_EOL;
echo '<script src="/js/component/modal/temperature_profiles_info.js"></script>' . PHP_EOL;
echo '<script src="/js/component/modal/time_to_detail.js"></script>' . PHP_EOL;
echo '<script src="/js/component/input.js"></script>' . PHP_EOL;
echo '<script src="/js/component/input/text.js"></script>' . PHP_EOL;
echo '<script src="/js/component/input/checkbox.js"></script>' . PHP_EOL;