1
0
mirror of https://github.com/beestat/app.git synced 2025-05-23 18:04:14 -04:00
beestat/api/runtime_thermostat_summary.php

367 lines
13 KiB
PHP
Executable File

<?php
/**
* Summary table for runtime_thermostat.
*
* @author Jon Ziebell
*/
class runtime_thermostat_summary extends cora\crud {
public static $exposed = [
'private' => [
'read_id',
'sync'
],
'public' => []
];
public static $cache = [
// Can't have these on right now because beestat loads and fires this API
// call off. Then it syncs all the data and calls these again to get updated
// data...if they're cached that just returns empty. So need to be able to
// bypass the cache or something to get that to work. Or just don't call the
// API call to begin with on first load if there's nothing there.
// 'read' => 3600, // 1 hour
// 'read_id' => 3600, // 1 hour
];
/**
* Read from the runtime_thermostat_summary table. Fixes temperature columns
* to return as decimals.
*
* @param array $attributes
* @param array $columns
*
* @return array
*/
public function read($attributes = [], $columns = []) {
$thermostats = $this->api('thermostat', 'read_id');
if(isset($attributes['thermostat_id']) === true) {
$thermostat_ids = [$attributes['thermostat_id']];
} else {
$thermostat_ids = array_keys($thermostats);
}
$runtime_thermostat_summaries = [];
foreach($thermostat_ids as $thermostat_id) {
$thermostat = $thermostats[$thermostat_id];
$attributes['thermostat_id'] = $thermostat_id;
// Get a base time for relative calculations
$local_time_zone = new DateTimeZone($thermostat['time_zone']);
$utc_time_zone = new DateTimeZone('UTC');
$date_time = new DateTime(date('Y-m-d H:i:s'), $utc_time_zone);
$date_time->setTimezone($local_time_zone);
if($date_time->getOffset() < 0) {
$base = strtotime($date_time->getOffset() . ' seconds');
} else {
$base = strtotime('+' . $date_time->getOffset() . ' seconds');
}
// If a date was given, apply the base time adjustment to correct for time
// zones.
if(isset($attributes['date']) === true) {
if(is_array($attributes['date']) === true) {
$attributes['date']['value'] = date(
'Y-m-d',
strtotime($attributes['date']['value'], $base)
);
} else {
$attributes['date'] = date(
'Y-m-d',
strtotime($attributes['date'], $base)
);
}
}
$runtime_thermostat_summaries = array_merge(
$runtime_thermostat_summaries,
parent::read($attributes, $columns)
);
}
foreach($runtime_thermostat_summaries as &$runtime_thermostat_summary) {
$runtime_thermostat_summary['avg_outdoor_temperature'] /= 10;
$runtime_thermostat_summary['min_outdoor_temperature'] /= 10;
$runtime_thermostat_summary['max_outdoor_temperature'] /= 10;
$runtime_thermostat_summary['avg_indoor_temperature'] /= 10;
$runtime_thermostat_summary['sum_heating_degree_days'] /= 10;
$runtime_thermostat_summary['sum_cooling_degree_days'] /= 10;
}
return $runtime_thermostat_summaries;
}
/**
* Populate from the max populated date until now.
*
* @param int $thermostat_id
*/
public function populate_forwards($thermostat_id) {
$thermostat = $this->api('thermostat', 'get', $thermostat_id);
$query = '
select
max(`date`) `max_date`
#"2021-01-20" `max_date`
from
`runtime_thermostat_summary`
where
`user_id` = ' . $this->database->escape($this->session->get_user_id()) . '
and `thermostat_id` = ' . $this->database->escape($thermostat_id) . '
';
$result = $this->database->query($query);
$row = $result->fetch_assoc();
if($row['max_date'] === null) {
if($thermostat['data_begin'] === null) {
$populate_begin = strtotime($thermostat['first_connected']);
} else {
$populate_begin = strtotime($thermostat['data_begin']); // Just grab everything
}
} else {
$populate_begin = strtotime($row['max_date']);
}
$populate_end = time();
$populate_begin = date('Y-m-d', $populate_begin);
$populate_end = date('Y-m-d', $populate_end);
return $this->populate($thermostat_id, $populate_begin, $populate_end);
}
/**
* Populate from the beginning of time until the min populated date.
*
* @param int $thermostat_id
*/
public function populate_backwards($thermostat_id) {
$thermostat = $this->api('thermostat', 'get', $thermostat_id);
// If there's no data do nothing.
if ($thermostat['data_begin'] === null) {
return;
}
$query = '
select
min(`date`) `min_date`
from
`runtime_thermostat_summary`
where
`user_id` = ' . $this->database->escape($this->session->get_user_id()) . '
and `thermostat_id` = ' . $this->database->escape($thermostat_id) . '
';
$result = $this->database->query($query);
$row = $result->fetch_assoc();
if($row['min_date'] === null) {
$populate_end = time();
} else {
// Include
$populate_end = strtotime($row['min_date']);
}
$populate_begin = strtotime($thermostat['data_begin']);
$populate_begin = date('Y-m-d', $populate_begin);
$populate_end = date('Y-m-d', $populate_end);
$this->populate($thermostat_id, $populate_begin, $populate_end);
}
/**
* Populate the runtime_thermostat_summary table.
*
* @param int $thermostat_id
* @param string $populate_begin Local date to begin populating, inclusive.
* @param string $populate_end Local date to end populating, inclusive.
*/
public function populate($thermostat_id, $populate_begin, $populate_end) {
$degree_days_base_temperature = 650; // 65°F * 10 (database storage)
$degree_days_base_time_interval = 5 / 1440; // 5 minutes out of 1440 minutes per day.
$thermostat = $this->api('thermostat', 'get', $thermostat_id);
// Convert date strings to timestamps to make them easier to work with.
$populate_begin = strtotime($populate_begin . ' 00:00:00');
$populate_end = strtotime($populate_end . ' 23:59:59');
$chunk_begin = $populate_begin;
$chunk_end = $populate_begin;
$data = [];
do {
$chunk_end = strtotime('+1 week', $chunk_begin);
// MySQL "between" is inclusive so go back 5 minutes to avoid
// double-counting rows.
$chunk_end = strtotime('-5 minute', $chunk_end);
// Don't overshoot into data that's already populated
$chunk_end = min($chunk_end, $populate_end);
$chunk_begin_datetime = get_utc_datetime(
date('Y-m-d H:i:s', $chunk_begin),
$thermostat['time_zone']
);
$chunk_end_datetime = get_utc_datetime(
date('Y-m-d H:i:s', $chunk_end),
$thermostat['time_zone']
);
$runtime_thermostats = $this->api(
'runtime_thermostat',
'read',
[
'attributes' => [
'thermostat_id' => $thermostat['thermostat_id'],
'timestamp' => [
'operator' => 'between',
'value' => [$chunk_begin_datetime, $chunk_end_datetime]
]
]
]
);
foreach($runtime_thermostats as $runtime_thermostat) {
$date = get_local_datetime(
$runtime_thermostat['timestamp'],
$thermostat['time_zone'],
'Y-m-d'
);
if(isset($data[$date]) === false) {
$data[$date] = [
'count' => 0,
'sum_fan' => 0,
'min_outdoor_temperature' => INF,
'max_outdoor_temperature' => -INF,
'sum_auxiliary_heat_1' => 0,
'sum_auxiliary_heat_2' => 0,
'sum_compressor_cool_1' => 0,
'sum_compressor_cool_2' => 0,
'sum_compressor_heat_1' => 0,
'sum_compressor_heat_2' => 0,
'sum_humidifier' => 0,
'sum_dehumidifier' => 0,
'sum_ventilator' => 0,
'sum_economizer' => 0,
'sum_heating_degree_days' => 0,
'sum_cooling_degree_days' => 0,
'avg_outdoor_temperature' => [],
'avg_outdoor_humidity' => [],
'avg_indoor_temperature' => [],
'avg_indoor_humidity' => []
];
}
if($runtime_thermostat['outdoor_temperature'] !== null) {
$runtime_thermostat['outdoor_temperature'] *= 10;
}
$runtime_thermostat['indoor_temperature'] *= 10;
$data[$date]['count']++;
$data[$date]['sum_fan'] += $runtime_thermostat['fan'];
if ($runtime_thermostat['outdoor_temperature'] !== null) {
$data[$date]['min_outdoor_temperature'] = min($runtime_thermostat['outdoor_temperature'], $data[$date]['min_outdoor_temperature']);
$data[$date]['max_outdoor_temperature'] = max($runtime_thermostat['outdoor_temperature'], $data[$date]['max_outdoor_temperature']);
}
$data[$date]['sum_auxiliary_heat_1'] += $runtime_thermostat['auxiliary_heat_1'];
$data[$date]['sum_auxiliary_heat_2'] += $runtime_thermostat['auxiliary_heat_2'];
if($runtime_thermostat['compressor_mode'] === 'cool') {
$data[$date]['sum_compressor_cool_1'] += $runtime_thermostat['compressor_1'];
$data[$date]['sum_compressor_cool_2'] += $runtime_thermostat['compressor_2'];
} else if($runtime_thermostat['compressor_mode'] === 'heat') {
$data[$date]['sum_compressor_heat_1'] += $runtime_thermostat['compressor_1'];
$data[$date]['sum_compressor_heat_2'] += $runtime_thermostat['compressor_2'];
}
if($runtime_thermostat['accessory_type'] === 'humidifier') {
$data[$date]['sum_humidifier'] += $runtime_thermostat['accessory'];
} else if($runtime_thermostat['accessory_type'] === 'dehumidifier') {
$data[$date]['sum_dehumidifier'] += $runtime_thermostat['accessory'];
} else if($runtime_thermostat['accessory_type'] === 'ventilator') {
$data[$date]['sum_ventilator'] += $runtime_thermostat['accessory'];
} else if($runtime_thermostat['accessory_type'] === 'economizer') {
$data[$date]['sum_economizer'] += $runtime_thermostat['accessory'];
}
if ($runtime_thermostat['outdoor_temperature'] !== null) {
$data[$date]['avg_outdoor_temperature'][] = $runtime_thermostat['outdoor_temperature'];
// CDD
if($runtime_thermostat['outdoor_temperature'] > $degree_days_base_temperature) {
$data[$date]['sum_cooling_degree_days'] +=
(($runtime_thermostat['outdoor_temperature']) - $degree_days_base_temperature) * $degree_days_base_time_interval;
}
// HDD
if($runtime_thermostat['outdoor_temperature'] < $degree_days_base_temperature) {
$data[$date]['sum_heating_degree_days'] +=
($degree_days_base_temperature - $runtime_thermostat['outdoor_temperature']) * $degree_days_base_time_interval;
}
}
if ($runtime_thermostat['outdoor_humidity'] !== null) {
$data[$date]['avg_outdoor_humidity'][] = $runtime_thermostat['outdoor_humidity'];
}
$data[$date]['avg_indoor_temperature'][] = $runtime_thermostat['indoor_temperature'];
$data[$date]['avg_indoor_humidity'][] = $runtime_thermostat['indoor_humidity'];
}
$chunk_begin = strtotime('+5 minute', $chunk_end);
} while ($chunk_end < $populate_end);
// Write to the database.
foreach($data as $date => &$row) {
if (count($row['avg_outdoor_temperature']) > 0) {
$row['avg_outdoor_temperature'] = round(array_sum($row['avg_outdoor_temperature']) / count($row['avg_outdoor_temperature']));
} else {
$row['avg_outdoor_temperature'] = null;
}
if (count($row['avg_outdoor_humidity']) > 0) {
$row['avg_outdoor_humidity'] = round(array_sum($row['avg_outdoor_humidity']) / count($row['avg_outdoor_humidity']));
} else {
$row['avg_outdoor_humidity'] = null;
}
if ($row['min_outdoor_temperature'] === INF) {
$row['min_outdoor_temperature'] = null;
}
if ($row['max_outdoor_temperature'] === -INF) {
$row['max_outdoor_temperature'] = null;
}
$row['avg_indoor_temperature'] = round(array_sum($row['avg_indoor_temperature']) / count($row['avg_indoor_temperature']));
$row['avg_indoor_humidity'] = round(array_sum($row['avg_indoor_humidity']) / count($row['avg_indoor_humidity']));
$row['date'] = $date;
$row['user_id'] = $thermostat['user_id'];
$row['thermostat_id'] = $thermostat['thermostat_id'];
$existing_runtime_thermostat_summary = $this->get([
'thermostat_id' => $thermostat['thermostat_id'],
'date' => $date
]);
if($existing_runtime_thermostat_summary === null) {
$this->create($row);
} else {
$row['runtime_thermostat_summary_id'] = $existing_runtime_thermostat_summary['runtime_thermostat_summary_id'];
$this->update($row);
}
}
}
}