From 5923ae89409f9a934593f552edf34150b48d7bbb Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Mon, 19 Jul 2010 17:33:26 +0200 Subject: [PATCH] improved interpreters added jpgraph output for debugging adding import script for f10's data (http://github.com/f10/volkszaehler.org) updated 1wire logging script --- .gitignore | 1 + backend/lib/Controller/Data.php | 11 +- backend/lib/Interpreter/Interpreter.php | 14 ++- backend/lib/Interpreter/Sensor.php | 2 +- backend/lib/Model/Data.php | 5 +- backend/lib/View/JpGraph.php | 100 ++++++++++++++++++ backend/lib/View/Json.php | 69 ++++++++++-- backend/lib/View/View.php | 27 +++-- share/tools/import_flo.php | 72 +++++++++++++ share/tools/{import.php => import_justin.php} | 9 +- share/tools/log1wire.sh | 63 ++++++++--- 11 files changed, 335 insertions(+), 38 deletions(-) create mode 100644 backend/lib/View/JpGraph.php create mode 100644 share/tools/import_flo.php rename share/tools/{import.php => import_justin.php} (88%) diff --git a/.gitignore b/.gitignore index a10ddb6..c1f80d8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ share/sql/demo/pulses.dummy.copy .project .buildpath .settings/ +backend/lib/vendor/* diff --git a/backend/lib/Controller/Data.php b/backend/lib/Controller/Data.php index 0388bc5..ee0d51a 100644 --- a/backend/lib/Controller/Data.php +++ b/backend/lib/Controller/Data.php @@ -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)); diff --git a/backend/lib/Interpreter/Interpreter.php b/backend/lib/Interpreter/Interpreter.php index 2c515f4..855286b 100644 --- a/backend/lib/Interpreter/Interpreter.php +++ b/backend/lib/Interpreter/Interpreter.php @@ -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 diff --git a/backend/lib/Interpreter/Sensor.php b/backend/lib/Interpreter/Sensor.php index b736b9f..bafa05c 100644 --- a/backend/lib/Interpreter/Sensor.php +++ b/backend/lib/Interpreter/Sensor.php @@ -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) { diff --git a/backend/lib/Model/Data.php b/backend/lib/Model/Data.php index 190ef89..2684711 100644 --- a/backend/lib/Model/Data.php +++ b/backend/lib/Model/Data.php @@ -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; /** diff --git a/backend/lib/View/JpGraph.php b/backend/lib/View/JpGraph.php new file mode 100644 index 0000000..1cfe132 --- /dev/null +++ b/backend/lib/View/JpGraph.php @@ -0,0 +1,100 @@ + + * + * 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(); + } + +} + +?> \ No newline at end of file diff --git a/backend/lib/View/Json.php b/backend/lib/View/Json.php index 0d188d6..d978703 100644 --- a/backend/lib/View/Json.php +++ b/backend/lib/View/Json.php @@ -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(), diff --git a/backend/lib/View/View.php b/backend/lib/View/View.php index 419f065..9d54236 100644 --- a/backend/lib/View/View.php +++ b/backend/lib/View/View.php @@ -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; } /* diff --git a/share/tools/import_flo.php b/share/tools/import_flo.php new file mode 100644 index 0000000..dba7683 --- /dev/null +++ b/share/tools/import_flo.php @@ -0,0 +1,72 @@ + +* +* 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() . '
'; + + flush(); + $pulses = array(); + } + } + }; + + fclose($fd); +} + +?> \ No newline at end of file diff --git a/share/tools/import.php b/share/tools/import_justin.php similarity index 88% rename from share/tools/import.php rename to share/tools/import_justin.php index a6f5635..5ca5a09 100644 --- a/share/tools/import.php +++ b/share/tools/import_justin.php @@ -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); diff --git a/share/tools/log1wire.sh b/share/tools/log1wire.sh index 1912a0e..e9e7343 100755 --- a/share/tools/log1wire.sh +++ b/share/tools/log1wire.sh @@ -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]}×tamp=$(( ${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]}×tamp=$(( ${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