2010-06-06 16:05:46 +02:00
< ? php
/*
* Copyright ( c ) 2010 by Justin Otherguy < justin @ justinotherguy . org >
*
* This program is free software ; you can redistribute it and / or modify it
* under the terms of the GNU General Public License ( either version 2 or
* version 3 ) as published by the Free Software Foundation .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*
* For more information on the GPL , please go to :
* http :// www . gnu . org / copyleft / gpl . html
*/
interface ChannelInterface {
// data management
public function addData ( $data );
public function getData ( $from = NULL , $to = NULL , $groupBy = NULL );
public function reset ();
// some statistical functions
public function getMin ( $from = NULL , $to = NULL );
public function getMax ( $from = NULL , $to = NULL );
public function getAverage ( $from = NULL , $to = NULL );
}
abstract class Channel extends DatabaseObject implements ChannelInterface {
const table = 'channels' ;
public function delete () {
2010-06-08 19:57:42 +02:00
$this -> reset (); // delete all data if database doesn't support foreign keys
2010-06-06 16:05:46 +02:00
parent :: delete ();
}
/*
2010-06-08 19:57:42 +02:00
* deletes all data from database
2010-06-06 16:05:46 +02:00
*/
public function reset ( $from = NULL , $to = NULL ) {
$this -> dbh -> execute ( 'DELETE FROM data WHERE channel_id = ' . ( int ) $this -> id ) . $this -> buildTimeFilter ( $from , $to );
}
/*
2010-06-08 19:57:42 +02:00
* add a new data to the database
2010-06-06 16:05:46 +02:00
*/
public function addData ( $data ) {
$sql = 'INSERT INTO data (channel_id, timestamp, value) VALUES(' . $this -> dbh -> escape ( $this ) . ', ' . $this -> dbh -> escape ( $data [ 'timestamp' ]) . ', ' . $this -> dbh -> escape ( $data [ 'value' ]) . ')' ;
$this -> dbh -> execute ( $sql );
}
/*
2010-06-08 19:57:42 +02:00
* This function retrieve data from the database . If desired it groups it into packages ( $groupBy parameter )
2010-06-06 16:05:46 +02:00
*
* @ return array () Array with timestamps => value ( sorted by timestamp from newest to oldest )
* @ param $groupBy determines how readings are grouped . Possible values are : year , month , day , hour , minute or an integer for the desired size of the returned array
*/
public function getData ( $from = NULL , $to = NULL , $groupBy = NULL ) {
2010-06-08 15:41:02 +02:00
$ts = 'FROM_UNIXTIME(timestamp/1000)' ; // just for saving space
2010-06-06 16:05:46 +02:00
switch ( $groupBy ) {
case 'year' :
$sqlGroupBy = 'YEAR(' . $ts . ')' ;
break ;
case 'month' :
$sqlGroupBy = 'YEAR(' . $ts . '), MONTH(' . $ts . ')' ;
break ;
case 'week' :
$sqlGroupBy = 'YEAR(' . $ts . '), WEEKOFYEAR(' . $ts . ')' ;
break ;
case 'day' :
$sqlGroupBy = 'YEAR(' . $ts . '), DAYOFYEAR(' . $ts . ')' ;
break ;
case 'hour' :
$sqlGroupBy = 'YEAR(' . $ts . '), DAYOFYEAR(' . $ts . '), HOUR(' . $ts . ')' ;
break ;
case 'minute' :
$sqlGroupBy = 'YEAR(' . $ts . '), DAYOFYEAR(' . $ts . '), HOUR(' . $ts . '), MINUTE(' . $ts . ')' ;
break ;
default :
2010-06-09 22:17:16 +02:00
if ( is_numeric ( $groupBy )) {
$groupBy = ( int ) $groupBy ;
}
2010-06-06 16:05:46 +02:00
$sqlGroupBy = false ;
}
$sql = 'SELECT' ;
$sql .= ( $sqlGroupBy === false ) ? ' timestamp, value' : ' MAX(timestamp) AS timestamp, SUM(value) AS value, COUNT(timestamp) AS count' ;
$sql .= ' FROM data WHERE channel_id = ' . ( int ) $this -> id . $this -> buildFilterTime ( $from , $to );
if ( $sqlGroupBy !== false ) {
$sql .= ' GROUP BY ' . $sqlGroupBy ;
}
2010-06-07 00:42:25 +02:00
$sql .= ' ORDER BY timestamp DESC' ;
2010-06-06 16:05:46 +02:00
$result = $this -> dbh -> query ( $sql );
$totalCount = $result -> count ();
if ( is_int ( $groupBy ) && $groupBy < $totalCount ) { // return $groupBy values
$packageSize = floor ( $totalCount / $groupBy );
$packageCount = $groupBy ;
}
else { // return all values or grouped by year, month, week...
$packageSize = 1 ;
$packageCount = $totalCount ;
}
$packages = array ();
$reading = $result -> rewind ();
for ( $i = 1 ; $i <= $packageCount ; $i ++ ) {
$package = array ( 'timestamp' => $reading [ 'timestamp' ], // last timestamp in package
2010-06-09 22:17:16 +02:00
'value' => ( float ) $reading [ 'value' ], // sum of values
2010-06-06 16:05:46 +02:00
'count' => ( $sqlGroupBy === false ) ? 1 : $reading [ 'count' ]); // total count of values or pulses in the package
while ( $package [ 'count' ] < $packageSize ) {
$reading = $result -> next ();
$package [ 'value' ] += $reading [ 'value' ];
$package [ 'count' ] ++ ;
}
2010-06-07 02:14:15 +02:00
2010-06-06 16:05:46 +02:00
$packages [] = $package ;
$reading = $result -> next ();
}
2010-06-07 00:42:25 +02:00
return array_reverse ( $packages ); // start with oldest ts and ends with newest ts (reverse array order due to descending order in sql statement)
2010-06-06 16:05:46 +02:00
}
/*
2010-06-07 00:42:25 +02:00
* simple self :: getByFilter () wrapper
2010-06-06 16:05:46 +02:00
*/
static public function getByType ( $type ) {
2010-06-08 15:41:02 +02:00
return self :: getByFilter ( array ( 'type' => $type ));
2010-06-06 16:05:46 +02:00
}
2010-06-07 00:42:25 +02:00
/*
* create new channel instance by given database query result
*/
2010-06-06 16:05:46 +02:00
final static protected function factory ( $object ) {
2010-06-09 21:53:55 +02:00
if ( ! class_exists ( $object [ 'type' ]) || ! is_subclass_of ( $object [ 'type' ], 'Channel' )) {
2010-06-09 21:11:26 +02:00
throw new InvalidArgumentException ( '\'' . $object [ 'type' ] . '\' is not a valid channel type' );
2010-06-06 16:05:46 +02:00
}
2010-06-14 00:41:04 +02:00
// code duplication from DatabaseObject::factory()
if ( ! isset ( self :: $instances [ self :: table ])) {
self :: $instances [ self :: table ] = array ();
}
if ( ! isset ( self :: $instances [ self :: table ][ $object [ 'id' ]])) {
self :: $instances [ self :: table ][ $object [ 'id' ]] = new $object [ 'type' ]( $object ); // create singleton instance of database object
}
return self :: $instances [ self :: table ][ $object [ 'id' ]]; // return singleton instance of database object
2010-06-09 21:48:18 +02:00
}
public function __get ( $key ) {
if ( $key == 'unit' ) { // TODO ugly code
return static :: unit ;
}
else {
return parent :: __get ( $key );
}
2010-06-06 16:05:46 +02:00
}
2010-06-07 00:42:25 +02:00
/*
* build simple timeframe filter
*/
2010-06-06 16:05:46 +02:00
static protected function buildFilterTime ( $from = NULL , $to = NULL ) {
$sql = '' ;
if ( is_int ( $to ) && $to <= time () * 1000 ) {
$sql .= ' && timestamp < ' . $to ;
}
if ( is_int ( $from ) && $from > 0 ) {
$sql .= ' && timestamp > ' . $from ;
}
return $sql ;
}
static protected function buildFilterQuery ( $filters , $conjunction , $columns = array ( 'id' )) {
2010-06-14 00:41:04 +02:00
$sql = 'SELECT ' . self :: table . '.* FROM ' . self :: table ;
2010-06-06 16:05:46 +02:00
// join groups
2010-06-14 07:41:41 +02:00
if ( preg_match ( '/^group\.([a-z_]+)$/' , $filters )) {
2010-06-14 00:41:04 +02:00
$sql .= ' LEFT JOIN channels_in_groups ON channels_in_groups.channel_id = ' . self :: table . '.id' ;
2010-06-14 07:41:41 +02:00
$sql .= ' LEFT JOIN groups ON groups.id = channels_in_groups.group_id' ;
$filters = preg_replace ( '/^group\.([a-z_]+)$/' , 'groups.$1' , $filters );
2010-06-06 16:05:46 +02:00
}
2010-06-14 00:41:04 +02:00
$sql .= self :: buildFilterCondition ( $filters , $conjunction );
2010-06-06 16:05:46 +02:00
return $sql ;
}
}