From 1e97616a3722d869bb088e922b1657f9df6106ff Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 28 Sep 2010 15:46:30 +0200 Subject: [PATCH] reworked data format and aggregation --- .../lib/Controller/AggregatorController.php | 12 ++- backend/lib/Controller/ChannelController.php | 12 ++- backend/lib/Controller/EntityController.php | 45 +++++++++- backend/lib/Interpreter/Interpreter.php | 40 ++++----- .../Iterator/DataAggregationIterator.php | 88 +++++++------------ .../lib/Interpreter/Iterator/DataIterator.php | 9 +- backend/lib/Interpreter/MeterInterpreter.php | 36 ++++---- backend/lib/Interpreter/SensorInterpreter.php | 20 +++-- backend/lib/Util/Debug.php | 8 +- backend/lib/Util/JSON.php | 1 + backend/lib/View/JSON.php | 11 ++- 11 files changed, 147 insertions(+), 135 deletions(-) diff --git a/backend/lib/Controller/AggregatorController.php b/backend/lib/Controller/AggregatorController.php index b8d6746..cc749b1 100644 --- a/backend/lib/Controller/AggregatorController.php +++ b/backend/lib/Controller/AggregatorController.php @@ -65,14 +65,12 @@ class AggregatorController extends EntityController { } else { // create new aggregator $aggregator = new Model\Aggregator('group'); // TODO support for other aggregator types - - foreach ($this->view->request->getParameters() as $parameter => $value) { - if (Definition\PropertyDefinition::exists($parameter)) { - $aggregator->setProperty($parameter, $value); - } - } - + $this->setProperties($aggregator); $this->em->persist($aggregator); + + if ($this->view->request->getParameter('setcookie')) { + $this->setCookie($channel); + } } $this->em->flush(); diff --git a/backend/lib/Controller/ChannelController.php b/backend/lib/Controller/ChannelController.php index d1b7dd8..fa6dd75 100644 --- a/backend/lib/Controller/ChannelController.php +++ b/backend/lib/Controller/ChannelController.php @@ -53,16 +53,14 @@ class ChannelController extends EntityController { */ public function add() { $channel = new Model\Channel($this->view->request->getParameter('type')); - - foreach ($this->view->request->getParameters() as $parameter => $value) { - if (Definition\PropertyDefinition::exists($parameter)) { - $channel->setProperty($parameter, $value); - } - } - + $this->setProperties($channel); $this->em->persist($channel); $this->em->flush(); + if ($this->view->request->getParameter('setcookie')) { + $this->setCookie($channel); + } + return $channel; } } diff --git a/backend/lib/Controller/EntityController.php b/backend/lib/Controller/EntityController.php index 683311f..360ae44 100644 --- a/backend/lib/Controller/EntityController.php +++ b/backend/lib/Controller/EntityController.php @@ -75,7 +75,48 @@ class EntityController extends Controller { */ public function edit($identifier) { $entity = $this->get($identifier); + $this->setProperties($entity); + $this->em->flush(); + return $entity; + } + + protected function setCookie(Model\Entity $entity) { + if ($uuids = $this->view->request->getParameter('uuids', 'cookies')) { + $uuids = Util\JSON::decode($uuids); + } + else { + $uuids = new Util\JSON(); + } + + // add new UUID + $uuids->append($entity->getUuid()); + + // remove duplicates + $uuids->exchangeArray(array_unique($uuids->getArrayCopy())); + + // send new cookie to browser + setcookie('uuids', $uuids->encode()); + } + + protected function unsetCookie(Model\Entity $entity) { + if ($uuids = $this->view->request->getParameter('uuids', 'cookies')) { + $uuids = Util\JSON::decode($uuids); + } + else { + $uuids = new Util\JSON(); + } + + // remove old UUID + $uuids->exchangeArray(array_filter($uuids->getArrayCopy, function($uuid) use ($entity) { + return $uuid != $entity->getUuid(); + })); + + // send new cookie to browser + setcookie('uuids', $uuids->encode()); + } + + protected function setProperties(Model\Entity $entity) { foreach ($this->view->request->getParameters() as $parameter => $value) { if (Definition\PropertyDefinition::exists($parameter)) { if ($value == '') { @@ -86,10 +127,6 @@ class EntityController extends Controller { } } } - - $this->em->flush(); - - return $entity; } } diff --git a/backend/lib/Interpreter/Interpreter.php b/backend/lib/Interpreter/Interpreter.php index 5b8e4a4..10105c4 100644 --- a/backend/lib/Interpreter/Interpreter.php +++ b/backend/lib/Interpreter/Interpreter.php @@ -59,7 +59,6 @@ abstract class Interpreter implements InterpreterInterface { $this->from = (isset($from)) ? self::parseDateTimeString($from, time() * 1000) : NULL; $this->to = (isset($to)) ? self::parseDateTimeString($to, (isset($this->from)) ? $this->from : time() * 1000) : NULL; - //Util\Debug::log('interval', $this->from, $this->to, strftime('%c', $this->from/1000), strftime('%c', $this->to/1000)); } /** @@ -68,43 +67,40 @@ abstract class Interpreter implements InterpreterInterface { * @param string|integer $groupBy * @return Volkszaehler\DataIterator */ - protected function getData($groupBy = NULL) { + protected function getData($tuples = NULL, $groupBy = NULL) { // get dbal connection from EntityManager $conn = $this->em->getConnection(); // prepare sql - $params = array(':id' => $this->channel->getId()); + $parameters = array(':id' => $this->channel->getId()); - $sqlFrom = ' FROM data'; - $sqlWhere = ' WHERE channel_id = :id' . self::buildDateTimeFilterSQL($this->from, $this->to); - $sqlOrderBy = ' ORDER BY timestamp ASC'; + $sql['from'] = ' FROM data'; + $sql['where'] = ' WHERE channel_id = :id' . self::buildDateTimeFilterSQL($this->from, $this->to); + $sql['orderBy'] = ' ORDER BY timestamp ASC'; - if ($sqlGroupBy = self::buildGroupBySQL($groupBy)) { - $sqlRowCount = 'SELECT COUNT(DISTINCT ' . $sqlGroupBy . ')' . $sqlFrom . $sqlWhere; - $sqlGroupBy = ' GROUP BY ' . $sqlGroupBy; - $sqlFields = ' MAX(timestamp) AS timestamp, SUM(value) AS value, COUNT(timestamp) AS count'; + if ($groupBy && $sql['groupFields'] = self::buildGroupBySQL($groupBy)) { + $sql['rowCount'] = 'SELECT COUNT(DISTINCT ' . $sql['groupFields'] . ')' . $sql['from'] . $sql['where']; + $sql['fields'] = ' MAX(timestamp) AS timestamp, SUM(value) AS value, COUNT(timestamp) AS count'; + $sql['groupBy'] = ' GROUP BY ' . $sql['groupFields']; } else { - $sqlRowCount = 'SELECT COUNT(*)' . $sqlFrom . $sqlWhere; - $sqlFields = ' timestamp, value'; + $sql['rowCount'] = 'SELECT COUNT(*)' . $sql['from'] . $sql['where']; + $sql['fields'] = ' timestamp, value, 1'; + $sql['groupBy'] = ''; } // get total row count for grouping - $rowCount = $conn->fetchColumn($sqlRowCount, $params, 0); + $rowCount = $conn->fetchColumn($sql['rowCount'], $parameters, 0); // query for data - $stmt = $conn->executeQuery('SELECT ' . $sqlFields . $sqlFrom . $sqlWhere . $sqlGroupBy . $sqlOrderBy, $params); + $stmt = $conn->executeQuery('SELECT ' . $sql['fields'] . $sql['from'] . $sql['where'] . $sql['groupBy'] . $sql['orderBy'], $parameters); // return iterators - if ($sqlGroupBy || is_null($groupBy)) { // aggregation by sql or skip it + if ($sql['groupBy'] || is_null($tuples) || $rowCount < $tuples) { return new Iterator\DataIterator($stmt, $rowCount); } - elseif (is_numeric($groupBy) ) { // aggregation by php - $tuples = (int) $groupBy; - return new Iterator\DataAggregationIterator($stmt, $rowCount, $tuples); - } else { - throw new \Exception('Invalid parameter: "groupBy"'); + return new Iterator\DataAggregationIterator($stmt, $rowCount, $tuples); } } @@ -163,11 +159,11 @@ abstract class Interpreter implements InterpreterInterface { $sql = ''; if (isset($from)) { - $sql .= ' && timestamp > ' . $from; + $sql .= ' && timestamp >= ' . $from; } if (isset($to)) { - $sql .= ' && timestamp < ' . $to; + $sql .= ' && timestamp <= ' . $to; } return $sql; diff --git a/backend/lib/Interpreter/Iterator/DataAggregationIterator.php b/backend/lib/Interpreter/Iterator/DataAggregationIterator.php index 5cc6c77..bc81da6 100644 --- a/backend/lib/Interpreter/Iterator/DataAggregationIterator.php +++ b/backend/lib/Interpreter/Iterator/DataAggregationIterator.php @@ -23,96 +23,72 @@ namespace Volkszaehler\Interpreter\Iterator; +use Volkszaehler\Util; + use Doctrine\DBAL; /** * @author Steffen Vogel * @package default */ -class DataAggregationIterator extends DataIterator { - protected $packageSize; // count of readings in tuple - protected $aggregatedSize; // total readings - protected $aggregatedKey = -1; +class DataAggregationIterator implements \Iterator, \Countable { + protected $current; + protected $key; // key + protected $size; // total readings in PDOStatement + protected $iterator; // subiterator /** * Constructor * * @param \PDOStatement $stmt - * @param unknown_type $size - * @param unknown_type $tuples + * @param integer $size + * @param integer $tuples */ - public function __construct(\PDOStatement $stmt, $size, $tuples) { - parent::__construct($stmt, $size); + public function __construct(\PDOStatement $stmt, $size, $tuples) { + $this->iterator = new DataIterator($stmt, $size); - if ($tuples < $this->size) { // return $tuples values - $this->packageSize = floor($this->size / $tuples); - $this->aggregatedSize = $tuples; - } - else { // return all values or grouped by year, month, week... - $this->packageSize = 1; - $this->aggregatedSize = $this->size; - } + $this->packageSize = floor($size / $tuples); + $this->size = (int) $tuples; } /** * Aggregate data */ public function next() { - $current = array (0, 0); + $current = array(0, 0, 0); + for ($i = 0; $i < $this->packageSize && $this->iterator->valid(); $i++, $this->iterator->next()) { + $tuple = $this->iterator->current(); - for ($c = 0; $c < $this->packageSize; $c++) { - parent::next(); - - if (parent::valid()) { - $tuple = parent::current(); - $current[1] += $tuple[1]; - } - else { - $this->current = FALSE; - return; - } + $current[0] = $tuple[0]; + $current[1] += $tuple[1]; + $current[2] += $tuple[2]; } - $this->aggregatedKey++; $this->current = $current; - $this->current[0] = $tuple[0]; // the last timestamp of a package - $this->current[2] = $this->packageSize; // how many pulses do we have aggregated? how accurate is our result? + $this->key++; } - /** - * @return array with data - */ - public function current() { - return $this->current; - } - - /** - * @return integer the nth data row - */ - public function key() { - return $this->aggregatedKey; - } - - /** - * Rewind the iterator - * - * Should only be called once - * PDOStatements doest support rewind() - */ public function rewind() { - parent::rewind(); - - $offset = $this->size - 1 - $this->aggregatedSize * $this->packageSize; - for ($i = 0; $i < $offset; $i++) { - parent::next(); + $this->iterator->rewind(); + // skip first readings to get an even divisor + $skip = count($this->iterator) - count($this) * $this->packageSize; + for ($i = 0; $i < $skip; $i++) { + $this->iterator->next(); } $this->next(); } + public function valid() { + return $this->key <= $this->size; + } + /** * Getter & setter */ public function getPackageSize() { return $this->packageSize; } + public function count() { return $this->size; } + public function key() { return $this->key; } + public function current() { return $this->current; } } ?> \ No newline at end of file diff --git a/backend/lib/Interpreter/Iterator/DataIterator.php b/backend/lib/Interpreter/Iterator/DataIterator.php index 864c8cf..03e409c 100644 --- a/backend/lib/Interpreter/Iterator/DataIterator.php +++ b/backend/lib/Interpreter/Iterator/DataIterator.php @@ -23,6 +23,8 @@ namespace Volkszaehler\Interpreter\Iterator; +use Volkszaehler\Util; + use Doctrine\DBAL; /** @@ -31,7 +33,7 @@ use Doctrine\DBAL; */ class DataIterator implements \Iterator, \Countable { protected $current; - protected $key; // incrementing key + protected $key; // key protected $stmt; // PDOStatement protected $size; // total readings in PDOStatement @@ -39,7 +41,7 @@ class DataIterator implements \Iterator, \Countable { * Constructor * * @param \PDOStatement $stmt - * @param unknown_type $size + * @param integer $size */ public function __construct(\PDOStatement $stmt, $size) { $this->size = $size; @@ -61,7 +63,6 @@ class DataIterator implements \Iterator, \Countable { public function next() { $this->key++; $this->current = $this->stmt->fetch(); - } /** @@ -75,7 +76,7 @@ class DataIterator implements \Iterator, \Countable { * @return boolean do we have another row in the resultset? */ public function valid() { - return (boolean) $this->current; + return is_array($this->current); } /** diff --git a/backend/lib/Interpreter/MeterInterpreter.php b/backend/lib/Interpreter/MeterInterpreter.php index adc0cf7..611dcfd 100644 --- a/backend/lib/Interpreter/MeterInterpreter.php +++ b/backend/lib/Interpreter/MeterInterpreter.php @@ -38,10 +38,10 @@ class MeterInterpreter extends Interpreter { /** * Calculates the consumption for interval speciefied by $from and $to * - * @todo untested + * @todo reimplement according to new env */ public function getConsumption() { - $sql = 'SELECT SUM(value) AS count + /*$sql = 'SELECT SUM(value) AS count FROM data WHERE channel_id = ' . (int) $this->id . ' && @@ -50,14 +50,15 @@ class MeterInterpreter extends Interpreter { $result = $this->dbh->query($sql)->rewind(); - return $result['count'] / $this->resolution / 1000; // returns Wh + return $result['count'] / $this->resolution / 1000; // returns Wh*/ } /** * @return array (0 => timestamp, 1 => value) + * @todo reimplement according to new env */ public function getMin() { - $data = $this->getData(); + /*$data = $this->getData(); $min = current($data); foreach ($data as $reading) { @@ -65,14 +66,15 @@ class MeterInterpreter extends Interpreter { $min = $reading; } } - return $min; + return $min;*/ } /** * @return array (0 => timestamp, 1 => value) + * @todo reimplement according to new env */ public function getMax() { - $data = $this->getData(); + /*$data = $this->getData(); $max = current($data); foreach ($data as $reading) { @@ -80,33 +82,25 @@ class MeterInterpreter extends Interpreter { $max = $reading; } } - return $max; + return $max;*/ } /** * @return float + * @todo reimplement according to new env */ public function getAverage() { - return $this->getConsumption() / ($this->to - $this->from) / 1000; // return W - } - - /** - * Just a passthrough of raw data - * - * @deprecated - */ - public function getPulses($groupBy = NULL) { - return parent::getData($groupBy); + //return $this->getConsumption() / ($this->to - $this->from) / 1000; // return W } /** * Raw pulses to power conversion * * @todo untested - * @return array with timestamp and values in [W] + * @return array with timestamp, values, and pulse count */ - public function getValues($groupBy = NULL) { - $pulses = parent::getData($groupBy); + public function getValues($tuples = NULL, $groupBy = NULL) { + $pulses = parent::getData($tuples, $groupBy); $values = array(); foreach ($pulses as $pulse) { @@ -134,7 +128,7 @@ class MeterInterpreter extends Interpreter { return array( ($next[0] - $delta / 2), // timestamp $next[1] * (3600000 / (($this->channel->getProperty('resolution') / 1000) * $delta)), // value - (isset($next[2])) ? $next[2] : 1 + $next[2] ); } } diff --git a/backend/lib/Interpreter/SensorInterpreter.php b/backend/lib/Interpreter/SensorInterpreter.php index 928de2b..e9c3f4d 100644 --- a/backend/lib/Interpreter/SensorInterpreter.php +++ b/backend/lib/Interpreter/SensorInterpreter.php @@ -29,21 +29,23 @@ namespace Volkszaehler\Interpreter; * @package default * @author Steffen Vogel */ +use Volkszaehler\Util; + class SensorInterpreter extends Interpreter { /** * @todo untested * @param string|integer $groupBy */ - public function getValues($groupBy = NULL) { - $data = parent::getData($groupBy); + public function getValues($tuples = NULL, $groupBy = NULL) { + $data = parent::getData($tuples, $groupBy); + $values = array(); foreach ($data as $reading) { - $count = isset($reading[2]) ? $reading[2] : 1; $values[] = array( - $reading[0], - $reading[1] / $count, // calculate average (ungroup the sql sum() function) - $count + (float) $reading[0], + (float) $reading[1] / $reading[2], + (int) $reading[2] ); } @@ -56,7 +58,7 @@ class SensorInterpreter extends Interpreter { * @return array (0 => timestamp, 1 => value) */ public function getMin() { - return $this->dbh->query('SELECT value, timestamp FROM data WHERE channel_id = ' . (int) $this->id . self::buildFilterTime($this->from, $this->to) . ' ORDER BY value ASC', 1)->current(); + //return $this->dbh->query('SELECT value, timestamp FROM data WHERE channel_id = ' . (int) $this->id . self::buildFilterTime($this->from, $this->to) . ' ORDER BY value ASC', 1)->current(); } /** @@ -65,7 +67,7 @@ class SensorInterpreter extends Interpreter { * @return array (0 => timestamp, 1 => value) */ public function getMax() { - return $this->dbh->query('SELECT value, timestamp FROM data WHERE channel_id = ' . (int) $this->id . self::buildFilterTime($this->from, $this->to) . ' ORDER BY value DESC', 1)->current(); + //return $this->dbh->query('SELECT value, timestamp FROM data WHERE channel_id = ' . (int) $this->id . self::buildFilterTime($this->from, $this->to) . ' ORDER BY value DESC', 1)->current(); } /** @@ -74,7 +76,7 @@ class SensorInterpreter extends Interpreter { * @return float */ public function getAverage() { - return $this->dbh->query('SELECT AVG(value) AS value FROM data WHERE channel_id = ' . (int) $this->id . self::buildFilterTime($this->from, $this->to))->current(); + //return $this->dbh->query('SELECT AVG(value) AS value FROM data WHERE channel_id = ' . (int) $this->id . self::buildFilterTime($this->from, $this->to))->current(); } /** diff --git a/backend/lib/Util/Debug.php b/backend/lib/Util/Debug.php index aa3a012..81f2273 100644 --- a/backend/lib/Util/Debug.php +++ b/backend/lib/Util/Debug.php @@ -85,11 +85,11 @@ class Debug { self::$instance->messages[] = array( 'message' => $message, - 'file' => $info['file'], - 'line' => $info['line'], - 'time' => date('r'), + //'file' => $info['file'], + //'line' => $info['line'], + //'time' => date('r'), 'args' => array_slice($info['args'], 1), - 'trace' => array_slice($trace, 1) + //'trace' => array_slice($trace, 1) ); } } diff --git a/backend/lib/Util/JSON.php b/backend/lib/Util/JSON.php index 38703ac..58bc6e0 100644 --- a/backend/lib/Util/JSON.php +++ b/backend/lib/Util/JSON.php @@ -35,6 +35,7 @@ define('JSON_PRETTY', 32); * @author Steffen Vogel */ class JSON extends \ArrayObject { + /** * OOP wrapper and factory * @param string $json diff --git a/backend/lib/View/JSON.php b/backend/lib/View/JSON.php index 286992f..0333b34 100644 --- a/backend/lib/View/JSON.php +++ b/backend/lib/View/JSON.php @@ -150,7 +150,16 @@ class JSON extends View { * @param Interpreter\InterpreterInterface $interpreter */ protected function addData(Interpreter\InterpreterInterface $interpreter) { - $this->json['data'][$interpreter->getUuid()] = $interpreter->getValues($this->request->getParameter('resolution')); + $data = $interpreter->getValues($this->request->getParameter('tuples'), $this->request->getParameter('group')); + + $this->json['data'][] = array( + 'uuid' => $interpreter->getUuid(), + 'count' => count($data), + 'min' => $interpreter->getMin(), + 'max' => $interpreter->getMax(), + 'average' => $interpreter->getAverage(), + 'tuples' => $data + ); } protected function addArray($data) {