reworked data format and aggregation

This commit is contained in:
Steffen Vogel 2010-09-28 15:46:30 +02:00
parent 3702d84d7c
commit 1e97616a37
11 changed files with 147 additions and 135 deletions

View file

@ -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();

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;

View file

@ -23,96 +23,72 @@
namespace Volkszaehler\Interpreter\Iterator;
use Volkszaehler\Util;
use Doctrine\DBAL;
/**
* @author Steffen Vogel <info@steffenvogel.de>
* @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; }
}
?>

View file

@ -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);
}
/**

View file

@ -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]
);
}
}

View file

@ -29,21 +29,23 @@ namespace Volkszaehler\Interpreter;
* @package default
* @author Steffen Vogel <info@steffenvogel.de>
*/
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();
}
/**

View file

@ -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)
);
}
}

View file

@ -35,6 +35,7 @@ define('JSON_PRETTY', 32);
* @author Steffen Vogel <info@steffenvogel.de>
*/
class JSON extends \ArrayObject {
/**
* OOP wrapper and factory
* @param string $json

View file

@ -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) {