mirror of
				https://github.com/beestat/app.git
				synced 2025-10-31 10:07:01 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			330 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			PHP
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			330 lines
		
	
	
		
			12 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;
 | |
|     }
 | |
| 
 | |
|     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.
 | |
|    */
 | |
|   private function populate($thermostat_id, $populate_begin, $populate_end) {
 | |
|     $degree_days_base_temperature = 65;
 | |
| 
 | |
|     $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_degree_days' => [],
 | |
|             'avg_outdoor_temperature' => [],
 | |
|             'avg_outdoor_humidity' => [],
 | |
|             'avg_indoor_temperature' => [],
 | |
|             'avg_indoor_humidity' => []
 | |
|           ];
 | |
|         }
 | |
| 
 | |
|         $runtime_thermostat['outdoor_temperature'] *= 10;
 | |
|         $runtime_thermostat['indoor_temperature'] *= 10;
 | |
| 
 | |
|         $data[$date]['count']++;
 | |
|         $data[$date]['sum_fan'] += $runtime_thermostat['fan'];
 | |
|         $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['compressor_mode'] === 'dehumidifier') {
 | |
|           $data[$date]['sum_dehumidifier'] += $runtime_thermostat['accessory'];
 | |
|         } else if($runtime_thermostat['compressor_mode'] === 'ventilator') {
 | |
|           $data[$date]['sum_ventilator'] += $runtime_thermostat['accessory'];
 | |
|         } else if($runtime_thermostat['compressor_mode'] === 'economizer') {
 | |
|           $data[$date]['sum_economizer'] += $runtime_thermostat['accessory'];
 | |
|         }
 | |
| 
 | |
|         if ($runtime_thermostat['outdoor_temperature'] !== null) {
 | |
|           $data[$date]['avg_outdoor_temperature'][] = $runtime_thermostat['outdoor_temperature'];
 | |
|           // $data[$date]['sum_degree_days'][] = $runtime_thermostat['outdoor_temperature'];
 | |
|         }
 | |
|         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) {
 | |
|       // $row['sum_degree_days'] = (array_mean($row['sum_degree_days']) / 10) - $degree_days_base_temperature;
 | |
|       $row['sum_heating_degree_days'] = 0;
 | |
|       $row['sum_cooling_degree_days'] = 0;
 | |
| 
 | |
|       $row['avg_outdoor_temperature'] = round(array_sum($row['avg_outdoor_temperature']) / count($row['avg_outdoor_temperature']));
 | |
|       $row['avg_outdoor_humidity'] = round(array_sum($row['avg_outdoor_humidity']) / count($row['avg_outdoor_humidity']));
 | |
|       $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);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 |