improved interpreters

added jpgraph output for debugging
adding import script for f10's data (http://github.com/f10/volkszaehler.org)
updated 1wire logging script
This commit is contained in:
Steffen Vogel 2010-07-19 17:33:26 +02:00
parent ea89630f73
commit 5923ae8940
11 changed files with 335 additions and 38 deletions

1
.gitignore vendored
View file

@ -3,3 +3,4 @@ share/sql/demo/pulses.dummy.copy
.project
.buildpath
.settings/
backend/lib/vendor/*

View file

@ -28,11 +28,20 @@ class Data extends Controller {
$q = $this->em->createQuery('SELECT c FROM Volkszaehler\Model\Channel\Channel c WHERE c.id IN (' . implode(', ', $ids) . ')');
$channels = $q->execute();
$from = ($this->view->request->getParameter('from')) ? (int) $this->view->request->getParameter('from') : NULL;
$to = ($this->view->request->getParameter('to')) ? (int) $this->view->request->getParameter('to') : NULL;
$groupBy = ($this->view->request->getParameter('groupBy')) ? $this->view->request->getParameter('groupBy') : NULL; // get all readings by default
switch ($this->view->request->getParameter('type')) {
case 'power':
case 'consumption':
case 'stats':
}
foreach ($channels as $channel) {
$interpreter = $channel->getInterpreter($this->em);
$this->view->add($interpreter->getValues($from, $to, $groupBy));

View file

@ -76,14 +76,22 @@ abstract class Interpreter implements InterpreterInterface {
$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->channel->getId(); // TODO add time filter
$sql .= ' FROM data WHERE channel_id = ' . (int) $this->channel->getId();
if (!is_null($from)) {
$sql .= ' && timestamp > ' . $from;
}
if (!is_null($to)) {
$sql .= ' && timestamp < ' . $to;
}
if ($sqlGroupBy !== false) {
$sql .= ' GROUP BY ' . $sqlGroupBy;
}
$sql .= ' ORDER BY timestamp DESC';
$rsm = new \Doctrine\ORM\Query\ResultsetMapping;
$rsm->addScalarResult('timestamp', 'timestamp');
$rsm->addScalarResult('value', 'value');
@ -108,7 +116,7 @@ abstract class Interpreter implements InterpreterInterface {
$packages = array();
$reading = reset($result);
for ($i = 1; $i <= $packageCount; $i++) {
$package = array('timestamp' => $reading['timestamp'], // last timestamp in package
$package = array('timestamp' => (int) $reading['timestamp'], // last timestamp in package
'value' => (float) $reading['value'], // sum of values
'count' => ($sqlGroupBy === false) ? 1 : $reading['count']); // total count of values or pulses in the package

View file

@ -23,7 +23,7 @@ namespace Volkszaehler\Interpreter;
class Sensor extends Interpreter {
public function getData($from = NULL, $to = NULL, $groupBy = NULL) {
public function getValues($from = NULL, $to = NULL, $groupBy = NULL) {
$data = parent::getData($from, $to, $groupBy);
array_walk($data, function(&$reading) {

View file

@ -36,7 +36,10 @@ class Data {
*/
private $timestamp;
/** @Column(type="decimal") */
/**
* @Column(type="decimal", precision="10", scale="5")
* @todo change to float after DCC-67 has been closed
*/
private $value;
/**

View file

@ -0,0 +1,100 @@
<?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 02139, USA.
*
* For more information on the GPL, please go to:
* http://www.gnu.org/copyleft/gpl.html
*/
namespace Volkszaehler\View;
require_once \Volkszaehler\BACKEND_DIR . '/lib/vendor/JpGraph/jpgraph.php';
require_once \Volkszaehler\BACKEND_DIR . '/lib/vendor/JpGraph/jpgraph_scatter.php';
require_once \Volkszaehler\BACKEND_DIR . '/lib/vendor/JpGraph/jpgraph_date.php';
class JpGraph extends View {
protected $width = 800;
protected $height = 600;
protected $plotCount = 0;
protected static $colors = array('chartreuse', 'chocolate1', 'cyan', 'blue', 'lightcyan4', 'gold');
protected $graph;
public function __construct(Http\Request $request, Http\Response $response, $format) {
parent::__construct($request, $response);
$this->graph = new \Graph($this->width,$this->height);
// Specify what scale we want to use,
$this->graph->SetScale('datlin');
$this->graph->SetMarginColor('white');
$this->graph->SetMargin(90,10,18,90);
$this->graph->SetTickDensity(TICKD_DENSE, TICKD_SPARSE);
$this->graph->xaxis->SetFont(FF_ARIAL);
$this->graph->yaxis->SetFont(FF_ARIAL);
$this->graph->xaxis->SetLabelAngle(45);
$this->graph->xaxis->SetLabelFormatCallback(function($label) { return date('j.n.y G:i', $label); });
//$this->graph->img->SetAntiAliasing();
}
public function add($data) {
$xData = $yData = array();
foreach ($data as $reading) {
$xData[] = $reading['timestamp']/1000;
$yData[] = $reading['value'];
}
// Create the linear plot
$plot = new \ScatterPlot($yData, $xData);
$plot->mark->SetColor(self::$colors[$this->plotCount]);
$plot->mark->SetFillColor(self::$colors[$this->plotCount]);
$plot->mark->SetType(MARK_DIAMOND);
$plot->mark->SetWidth(1);
$plot->SetLinkPoints(true, self::$colors[$this->plotCount]);
$this->plotCount++;
// Add the plot to the graph
$this->graph->Add($plot);
}
public function addException(\Exception $e) { echo $e; }
public function addDebug() {}
public static function factory(Http\Request $request, Http\Response $response) {
}
public function render() {
// Display the graph
$this->graph->Stroke();
$this->response->send();
}
}
?>

View file

@ -34,27 +34,78 @@ class Json extends View {
$this->json['source'] = 'volkszaehler.org';
$this->json['version'] = \Volkszaehler\VERSION;
$this->response->setHeader('Content-type', 'application/json');
}
public function render() {
parent::render();
echo json_encode($this->json);
echo self::format(json_encode($this->json));
}
protected function addDebug() {
protected static function format($json) {
$tab = "\t";
$formatted = '';
$indentLevel = 0;
$inString = false;
$len = strlen($json);
for($c = 0; $c < $len; $c++) {
$char = $json[$c];
switch($char) {
case '{':
case '[':
$formatted .= $char;
if (!$inString && (ord($json[$c+1]) != ord($char)+2)) {
$indentLevel++;
$formatted .= "\n" . str_repeat($tab, $indentLevel);
}
break;
case '}':
case ']':
if (!$inString && (ord($json[$c-1]) != ord($char)-2)) {
$indentLevel--;
$formatted .= "\n" . str_repeat($tab, $indentLevel);
}
$formatted .= $char;
break;
case ',':
$formatted .= $char;
if (!$inString) {
$formatted .= "\n" . str_repeat($tab, $indentLevel);
}
break;
case ':':
$formatted .= $char;
if (!$inString) {
$formatted .= ' ';
}
break;
case '"':
if ($c > 0 && $json[$c-1] != '\\') {
$inString = !$inString;
}
default:
$formatted .= $char;
break;
}
}
return $formatted;
}
public function addDebug() {
$config = Util\Registry::get('config');
$this->json['debug'] = array('time' => $this->getTime(),
'database' => array('driver' => $config['db']['driver'],
'queries' => Util\Debug::getSQLLogger()->queries)
);
);
}
protected function addException(\Exception $exception) {
public function addException(\Exception $exception) {
$this->json['exception'] = array('type' => get_class($exception),
'message' => $exception->getMessage(),
'code' => $exception->getCode(),

View file

@ -21,7 +21,12 @@
namespace Volkszaehler\View;
abstract class View {
interface ViewInterface {
public function addException(\Exception $e);
public function addDebug();
}
abstract class View implements ViewInterface {
public $request;
protected $response;
@ -43,15 +48,23 @@ abstract class View {
* creates new view instance depending on the requested format
*/
public static function factory(Http\Request $request, Http\Response $response) {
$format = ucfirst(strtolower($request->getParameter('format')));
$controller = ucfirst(strtolower($request->getParameter('controller')));
$format = strtolower($request->getParameter('format'));
$controller = strtolower($request->getParameter('controller'));
$viewClassName = 'Volkszaehler\View\\' . $format . '\\' . $controller;
if (!(\Volkszaehler\Util\ClassLoader::classExists($viewClassName)) || !is_subclass_of($viewClassName, '\Volkszaehler\View\View')) {
throw new \InvalidArgumentException('\'' . $viewClassName . '\' is not a valid View');
if (in_array($format, array('png', 'jpg'))) {
$view = new JpGraph($request, $response, $format);
}
else {
$viewClassName = 'Volkszaehler\View\\' . ucfirst($format) . '\\' . ucfirst($controller);
if (!(\Volkszaehler\Util\ClassLoader::classExists($viewClassName)) || !is_subclass_of($viewClassName, '\Volkszaehler\View\View')) {
throw new \InvalidArgumentException('\'' . $viewClassName . '\' is not a valid View');
}
$view = new $viewClassName($request, $response);
}
return new $viewClassName($request, $response);
return $view;
}
/*

View file

@ -0,0 +1,72 @@
<?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 02139, USA.
*
* For more information on the GPL, please go to:
* http://www.gnu.org/copyleft/gpl.html
*/
/*
* simple script to import demo pulses
*/
$sql = '';
$pulses = array();
// initialize db connection
mysql_connect('localhost', 'vz', 'demo');
mysql_select_db('volkszaehler_doctrine');
// dump => db channel id mapping
$mapping[4] = 22;
$mapping[5] = 23;
$mapping[9] = 24;
$mapping[10] = 25;
$fd = fopen('/home/steffen/Desktop/testdaten_nicht_veroeffentlichen.sql', 'r');
if ($fd) {
while (!feof($fd)) {
$line = fgets($fd);
// $matches index 1 2 3 4 5 6 7 8
if (preg_match('/^\((\d), \'(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})\', (\d)/', $line, $matches)) {
$ts = mktime($matches[5], $matches[6], $matches[7], $matches[3], $matches[4], $matches[2]) * 1000;
$value = $matches[8];
$channel = $mapping[$matches[1]];
if ($ts > 0) {
$pulses[] = '(' . $channel . ', ' . $ts . ', ' . $value . ')';
}
if (count($pulses) % 1000 == 0) {
$sql = 'INSERT INTO data (channel_id, timestamp, value) VALUES ' . implode(', ', $pulses);
if (!mysql_query($sql)){
echo mysql_error();
}
echo 'Rows inserted: ' . mysql_affected_rows() . '<br />';
flush();
$pulses = array();
}
}
};
fclose($fd);
}
?>

View file

@ -23,9 +23,7 @@
* simple script to import demo pulses
*/
include '../../backend/init.php';
$dbh = Database::getConnection();
// TODO adapt to doctrine dal or use native mysql
$sql = '';
@ -42,11 +40,14 @@ if ($fd) {
fclose($fd);
$sql = 'INSERT INTO data (meter_id, timestamp, value) VALUES ' . implode(', ', $pulses);
$sql = 'INSERT INTO data (channel_id, timestamp, value) VALUES ' . implode(', ', $pulses);
$dbh->execute($sql);
echo 'Imported rows: ' . $dbh->affectedRows();
}
else {
throw new Exception('cant open dump');
}
function parsePgSqlTimestamp($timestamp) {
$unix = strtotime($timestamp);

View file

@ -29,23 +29,45 @@
# configuration
#
# backend url
URL="http://localhost/workspace/volkszaehler.org/content/backend.php"
# URL_PARAMS="&debug=1"
URL="http://localhost/workspace/volkszaehler.org/backend/index.php"
# 1wire sensor id => volkszaehler.org ucid
declare -A MAPPING
MAPPING["1012E6D300080077"]="93f85330-9037-11df-86d3-379c018a387b"
# the digitemp binary, choose the right one for your adaptor
DIGITEMP="digitemp_DS9097"
DIGITEMP_OPTS="-a"
# DIGITEMP_OPTS="-t 0"
# the digitemp configuration (holds your sensor ids)
DIGITEMP_CONF="/home/steffen/.digitemprc"
# the port of your digitemp adaptor
DIGITEMP_PORT="/dev/ttyUSB0"
# additional options for digitemp
# specify single or all sensors here for example
DIGITEMP_OPTS="-t 0"
#DIGITEMP_OPTS="-a"
# additional options for curl
# specify credentials, proxy etc here
CURL_OPTS=""
# enable this for a more verbose output
#DEBUG=true
# ========================= do not change anything under this line
# special ucid prefix for 1wire sensors
UCID_PREFIX="07506920-6e7a-11df-"
# building digitemp options
DIGITEMP_OPTS="-c ${DIGITEMP_CONF} ${DIGITEMP_OPTS} -s ${DIGITEMP_PORT} -q -o %s;%R;%N;%C"
if [ $DEBUG ]; then
echo "enabling debugging output"
echo -e "running digitemp:\t${DIGITEMP} ${DIGITEMP_OPTS}"
fi
# execute digitemp
LINES=$(${DIGITEMP} -c ${DIGITEMP_CONF} ${DIGITEMP_OPTS} -q -o "%N;%R;%C")
LINES=$(${DIGITEMP} ${DIGITEMP_OPTS})
# save old internal field seperator
OLD_IFS=${IFS}
@ -55,9 +77,26 @@ for LINE in $LINES
do
IFS=";"
COLUMNS=( $LINE )
UCID="${UCID_PREFIX}${COLUMNS[1]:0:4}-${COLUMNS[1]:5}"
curl ${CURL_OPTS} "${URL}?controller=data&action=add&ucid=${UCID}&value=${COLUMNS[2]}&timestamp=$(( ${COLUMNS[0]} * 1000 ))${URL_PARAMS}"
done
IFS=${OLD_IFS}
# reset old ifs
IFS=${OLD_IFS}
if [ -z ${MAPPING[${COLUMNS[1]}]} ]; then
echo "sensor ${COLUMNS[1]} is not mapped to an ucid" >&2
echo "please add it to the script. Example:" >&2
echo >&2
echo -e "MAPPING[\"${COLUMNS[1]}\"]=\"9aa643b0-9025-11df-9b68-8528e3b655ed\"" >&2
elif [ ${COLUMNS[3]:0:2} == "85" ]; then
echo "check your wiring; we received an invalid reading!" 1>2&
else
UCID=${MAPPING[${COLUMNS[1]}]}
REQUEST_URL="${URL}?format=json&controller=data&action=add&ucid=${UCID}&value=${COLUMNS[3]}&timestamp=$(( ${COLUMNS[2]} * 1000 ))${URL_PARAMS}${DEBUG:+&debug=1}"
if [ $DEBUG ]; then
echo -e "logging sensor:\t\t${UCID}"
echo -e "with value:\t\t${COLUMNS[3]}"
echo -e "at\t\t\t$(date -d @${COLUMNS[2]})"
echo "|"
fi
curl ${CURL_OPTS} ${DEBUG:-"-s"} ${DEBUG:-"-o /dev/null"} ${DEBUG:+"--verbose"} "${REQUEST_URL}" 2>&1 | sed 's/^/|\t/'
fi
done