reworked data format and aggregation
This commit is contained in:
parent
3702d84d7c
commit
1e97616a37
11 changed files with 147 additions and 135 deletions
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
|
||||
?>
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Add table
Reference in a new issue