<?php
namespace SmartData;
require_once( __DIR__ . '/Exception.php');
final class Aggregator
{
public static function new($parameters){
try {
if(!isset($parameters->name))
return null;
switch ($parameters->name) {
case 'dbp':
case 'lastValue':
case 'stdbp':
return new Trickle($parameters);
case 'min':
case 'max':
return new Limit($parameters);
case 'mean':
return new Mean($parameters);
case 'lowerThan':
return new LowerThan($parameters);
case 'higherThan':
return new HigherThan($parameters);
case 'confidence':
return new Confidence($parameters);
case 'can':
return new CAN($parameters);
default:
break;
}
} catch (Exception\CantCreateException $e) {
return null;
}
}
}
interface iAggregator
{
public function __construct($parameters);
public function aggregate(SmartData $smartdata);
public function finish();
}
class Confidence implements iAggregator
{
public function __construct($parameters) { }
public function aggregate(SmartData $smartdata) {
$smartdata->value = $smartdata->confidence;
return $smartdata;
}
public function finish() { return null; }
}
abstract class Threshold implements iAggregator
{
public function __construct($parameters) {
if (isset($parameters->parameter)){
$this->threshold = $parameters->parameter;
return $this;
} else {
throw (new Exception\CantCreateException());
}
}
public function finish() { return null; }
protected $threshold;
}
class LowerThan extends Threshold
{
public function aggregate(SmartData $smartdata) {
return ($smartdata->value > $this->threshold) ? null : $smartdata;
}
}
class HigherThan extends Threshold
{
public function aggregate(SmartData $smartdata) {
return ($smartdata->value < $this->threshold) ? null : $smartdata;
}
}
class Trickle implements iAggregator
{
public function __construct($parameters) {
if (isset($parameters)){
switch ($parameters->name) {
case 'dbp':
$e = isset($parameters->parameter) ? $parameters->parameter : 2;
$this->predictor = new DBP(10, 3, $e, $e, 0);
break;
case 'lastValue':
$threshold = isset($parameters->parameter) ? $parameters->parameter : 0;
$this->predictor = new LastValue($threshold);
break;
default:
throw (new Exception\CantCreateException());
break;
}
$this->count_models = isset($parameters->range);
} else {
throw (new Exception\CantCreateException());
}
}
public function aggregate(SmartData $smartdata) {
$this->predictor->trickle($smartdata->time, $smartdata->value);
$smartdata->value = $this->predictor->predict($smartdata->time);
if($this->count_models && !$this->predictor->new_model)
return null;
return $smartdata;
}
public function finish() { return null; }
private $predictor;
private $count_models;
}
class Limit implements iAggregator
{
private const Min = 0;
private const Max = 1;
public function __construct($parameters) {
$this->begin = $this->selected = null;
if(isset($parameters->range) && $parameters->range > 0) {
$this->range = $parameters->range;
switch ($parameters->name) {
case 'min': $this->limit = Limit::Min; break;
case 'max': $this->limit = Limit::Max; break;
default:
throw (new Exception\CantCreateException());
break;
}
} else {
throw (new Exception\CantCreateException());
}
}
public function aggregate(SmartData $smartdata) {
if($this->begin == null) {
$this->begin = $smartdata->time;
$this->selected = clone $smartdata;
} else {
if($smartdata->time > ($this->begin+$this->range)) {
$ret = clone $this->selected;
$this->begin = $this->last;
$this->selected = clone $smartdata;
return $ret;
} else {
if( ($this->limit == Limit::Min && $smartdata->value < $this->selected->value) ||
($this->limit == Limit::Max && $smartdata->value > $this->selected->value) )
$this->selected = clone $smartdata;
}
}
$this->last = $smartdata->time;
return null;
}
public function finish() { return null; }
private $begin;
private $last;
private $range;
private $selected;
private $limit;
}
class Mean implements iAggregator
{
public function __construct($parameters) {
$this->begin = null;
$this->count = $this->sum = 0;
if(isset($parameters->range) && $parameters->range > 0) {
$this->range = $parameters->range;
} else {
throw (new Exception\CantCreateException());
}
}
public function aggregate(SmartData $smartdata) {
$ret = null;
if($this->begin == null) {
$this->begin = $smartdata->time;
} else {
if($smartdata->time > ($this->begin+$this->range)) {
$ret = clone $this->last;
$ret->time = (int)($this->begin + $this->last->time)/2;
$ret->value = $this->sum/$this->count;
$this->begin = $smartdata->time;
$this->count = $this->sum = 0;
}
}
$this->last = clone $smartdata;
$this->sum += (float)$smartdata->value;
$this->count++;
return $ret;
}
public function finish() { return null; }
private $range;
private $begin;
private $last;
private $sum;
private $count;
}
class Cirular_Queue
{
public function __construct($size_limit) {
$this->_head = -1;
$this->_tail = -1;
$this->_limit = $size_limit;
$this->_queue = array();
}
public function insert($el) {
if($this->full()) {
$this->_head = ($this->_head + 1) % $this->_limit;
$this->_tail = ($this->_tail + 1) % $this->_limit;
} else {
if($this->empty())
$this->_head = $this->_tail = 0;
else
$this->_tail = ($this->_tail + 1) % $this->_limit;
$this->_size++;
}
$this->_queue[$this->_tail] = $el;
return $el;
}
public function size() { return $this->_size; }
public function max_size() { return $this->_limit; }
public function head() { return $this->_queue[$this->$this->_head]; }
public function tail() { return $this->_queue[$this->_tail]; }
public function at($index){ return $this->_queue[($this->_head + $index) % $this->_limit];}
public function empty() { return ($this->_head == -1 && $this->_tail == -1); }
public function full() { return (($this->_tail + 1) % $this->_limit) == $this->_head; }
public function clear() {
$this->_size = 0;
$this->_head = $this->_tail = -1;
}
public function print_r() {
for($i = 0; $i < $this->_size; $i++){
echo $this->at($i)." | ";
}
echo "\n";
}
private $_queue;
private $_size;
private $_limit;
private $_head;
private $_tail;
}
class Entry
{
public function __construct($time=0, $value=0) {
$this->_time = $time;
$this->_value = $value;
}
public function time() { return $this->_time; }
public function value() { return $this->_value; }
private $_time;
private $_value;
public function __toString() {
return "[{$this->_time}]($this->_value)";
}
}
class Model
{
public function __construct($a=0, $b=0, $t0=0) {
$this->_a = $a;
$this->_b = $b;
$this->_t0 = $t0;
}
public function a($a) { $this->_a = $a; }
public function b($b) { $this->_b = $b; }
public function t0($t0) { $this->_t0 = $t0; }
public function compute($t) {
return ($this->_a * ($t - $this->_t0) + $this->_b);
}
private $_a;
private $_b;
private $_t0;
public function __toString() {
return "[{$this->_a},{$this->_b},{$this->_t0}]";
}
}
class DBP
{
public function __construct($w, $l, $r, $a, $t) {
$this->_w = $w;
$this->_l = $l;
$this->_r = $r;
$this->_a = $a;
$this->_t = $t;
$this->_model = null;
$this->_history = new Cirular_Queue($w);
$this->new_model = false;
$this->_timer = 0;
}
public function predict($t) {
if($this->_model != null)
return $this->_model->compute($t);
else if(!$this->_history->empty())
return $this->_history->tail()->value();
else
return 0;
}
public $new_model;
public function trickle($time, $value) {
$this->new_model = false;
$this->_history->insert(new Entry($time, $value));
if($this->_model == null) {
if($this->_history->full()){
$this->build_model($time, $value);
return false;
}
} else {
$predicted = $this->predict($time);
$max_acceptable_error = $this->max($this->abso(($value * (float)$this->_r)/100), (float)$this->_a );
$error = $this->abso( (float)$value - $predicted );
if($error > $max_acceptable_error && (++$this->_timer) > $this->_t){
$this->_timer = 0;
$this->build_model($time, $value);
return false;
}
}
return true;
}
private function build_model($time, $value) {
if(!$this->_history->full())
die ("The history must be full");
$this->new_model = true;
if($this->_model == null)
$this->_model = new Model();
$avg_oldest = 0;
$avg_recent = 0;
$this->_model->t0($this->_history->at(0)->time());
for($i = 0; $i < $this->_l; $i++)
$avg_oldest += $this->_history->at($i)->value();
$avg_oldest /= $this->_l;
for($i = 0; $i < $this->_l; $i++)
$avg_recent += $this->_history->at($this->_history->size()-1-$i)->value();
$avg_recent /= $this->_l;
$t_oldest = ( $this->_history->at(0)->time() + $this->_history->at($this->_l-1)->time() )/2;
$t_recent = ( $this->_history->at($this->_history->size()-1)->time() + $this->_history->at($this->_history->size()-1-($this->_l-1))->time() )/2;
$this->_model->a(($avg_recent - $avg_oldest)/($t_recent - $t_oldest));
$this->_model->b($avg_oldest);
$this->_model->t0($t_oldest);
$predicted = $this->predict($time);
$error = $value - $predicted;
$this->_model->b($avg_oldest+$error);
}
public function print() {
$this->_history->print_r();
}
private function abso($a){ return ($a < 0) ? -$a : $a; }
private function max($a, $b) { return ($a >= $b) ? $a : $b; }
private function min($a, $b) { return ($a <= $b) ? $a : $b; }
private $_w;
private $_l;
private $_r;
private $_a;
private $_t;
private $_model;
private $_history;
private $_timer;
}
class LastValue extends DBP
{
public function __construct($threshold) {
parent::__construct(10, 3, $threshold, $threshold, 0);
$this->_last_value = 0;
}
private $_last_value;
}
class CAN implements iAggregator
{
public function __construct($parameters) {
if (isset($parameters->parameter)){
$this->id = $parameters->parameter;
$this->offset = $parameters->range;
return $this;
} else {
throw (new Exception\CantCreateException());
}
}
function chbo($num) {
$data = dechex($num);
if (strlen($data) <= 2) {
return $num;
}
$u = unpack("H*", strrev(pack("H*", $data)));
$f = hexdec($u[1]);
return $f;
}
public function aggregate(SmartData $smartdata) {
$value = (int)$smartdata->value;
switch ((int)$this->id) {
case 401:
switch (((int)$this->offset)/1000000) {
case 1:
$value = $value & 0xFFFF;
break;
case 2:
$value = ($value >> 16) & 0xFFFF;
break;
default:
break;
}
break;
case 402:
switch (((int)$this->offset)/1000000) {
case 1:
$value = abs($value >> (6*8));
break;
case 2:
//$value = abs($value >> (4*8));
break;
case 3:
//$value = ($value >> ((5*8)-1)) & 1;
break;
case 4:
//$value = ($value >> (6*8)) & 1;
default:
break;
}
break;
case 403:
switch (((int)$this->offset)/1000000) {
case 1:
$value = $value & 0xFFFF;
break;
case 2:
$value = ($value >> 16) & 0xFFFF;
break;
case 3:
$value = ($value >> 32) & 0xFFFF;
break;
case 4:
$value = ($value >> 48) & 0xFFFF;
default:
break;
}
break;
case 411:
break;
case 412:
break;
case 413:
break;
case 414:
break;
case 415:
break;
default:
# code...
break;
}
$smartdata->value = $value;
return $smartdata;
}
public function finish() { return null; }
protected $id;
protected $offset;
}