Go Down

Topic: How to graph sensor data with rrdtool and the Bridge (Read 10682 times) previous topic - next topic


The Arduin Yun has a builtin web server. One can install an old version of rrdtool on linino. And the Bridge is here to transmit values from the 32u4 to linino. We will exploit this to visualize at the address http://arduino.local/sd/ a graph of a sensor value. For this example the value is transmitted every five minutes. It is the temperature in my kitchen.

  • Install rrdtool1 package through the advanced configure interface of the Yun.

  • You must have a microSD. Make an /mnt/sda1/arduino/scripts directory. Make another /mnt/sda1/arduino/rrd directory. Make a /mnt/sda1/arduino/www directory.

  • Write a sketch that updates the value. In setup you must initialize the Bridge.
Code: [Select]
void setup(){

Somewhere in loop one have to update the value. For a float value for example. Other methods exist with the String class.
Code: [Select]

#include <stdlib.h>

void loop() {
  float temperature;
  char buf_tmp[8];
   Bridge.put("temperature", buf_tmp);

  • Create the round robbin database. In the rrd repertory :
    rrdtool create temperature.rrd --step 300 DS:temp:GAUGE:600:0:50 RRA:AVERAGE:0.5:1:576

  • Create an index.html in /mnt/sda1/arduino/www directory with <img src="cuisine.png"></img> somewhere.

  • A python script will be in charge to update the database and to recompute regularly the cuisine.png file. This script is to be launched in background.
Code: [Select]

# -*- coding: iso-8859-15 -*-

import sys   
import subprocess
import time

sys.path.insert(0, '/usr/lib/python2.7/bridge/')
import json
from bridgeclient import BridgeClient as bridgeclient
passerelle = bridgeclient()                             
while True:
        temperature = passerelle.get('temperature')

        # run shell command : rrdtool update temperature.rrd N:latemperature
        subprocess.check_call(["rrdupdate", "../rrd/temperature.rrd", "N:" + temperature])

        # generate the graph
        subprocess.check_call(["rrdtool", "graph", "../www/cuisine.png", "-a", "PNG",
                               "-w", "600", "-h", "300", "-l",  "15", "-u", "24", "-r",
                               '--vertical-label', 'Degre Celsius',
    except Exception as e:
        print 'Error : ', e

That's all.

  • Pro : no extra work to adapt your sketch to have your graph. Your graph is local, you don't have to use an external site like Xively.

  • Cons : I personally don't like very much graphes produced by rrdtool graph. But it is possible to make your own with some svg for example. It is not time accurate. At some time the sketch update the value and in an interval of five minutes the graph is updated so the incertitude on time is of five minutes.

:~ It requires that you are able to edit a file on linino and to launch a script or manage directories. So it is better if you known linux a little bit. (to edit file on linino I use the TRAMP mode of emacs, it is very handy). You also have to edit a minimal html file.

That's all. IMHO Arduino Yun which permits such a thing is a very nice tool.

Hope this help.

Alexandre from Marseilles


Thanks for sharing.

I am just considering offer a graph in a index.htm web page. To do it I am studying javascript and google charts.

The values to be plotted can be stored in a file, or taken from google spreadsheet, or just passed from the linino side as a a vector.

And now a new option!

Any advantages and dissavnatages of these methods?


Hello ProfePaco,
If you source your data from a spreadsheet, your use case is different from the mine.
My use case is :

  • The 32u4 side of the Yun produce one measure every five minutes. It passes the value to the linino side thanks to the Bridge.

  • On the linino side a script store values in an rrd database and produce a fresh png file every five minutes.

  • By default the Arduino Yun has a web server running. So I just have to fill an html file which has a link on the graph.

The advantage of a rrd database against a file is the circularity of the database. You don't have to manage the growing in size of the file. It is at the price of a little bit tricky rrdtool create command. But it is worth trying.
For my use case a simple file for storing the database is not a good solution.
You mention also a solution with a vector of values. For my use case it is neither a good solution because it implies that the 32u4 side is managing many values but it has scared ressources.
I have never use google spreadsheet so i can't say nothing on it.
I have never use google chart API but I think i will try it. Instead of creating a png file with rrdtool graph, I will create a data file in my python script by calling rrdtool dump or rrdtool xport. This file will be read by a javascript code to produce a chart with a google chart API. If I succeed in that, I will share it.

Hope this help.



Dec 08, 2013, 08:10 pm Last Edit: Dec 08, 2013, 08:13 pm by bjarne Reason: 1
AlexandreB, thanks for sharing.

I took a slightly different approach to essentially the same scenario.  I am developing a controller for my greenhouse and I want to log the data every five minutes.  So my steps are:
1. Every five minutes the Arduino collects the sensor data and adds a new line in a csv file in a dedicated directory on the SD card.
2. A new file is started every day at midnight (with the date stamp as the file name).
3. I am invoking a PHP file from the web server, which parses the data and plots it using the jsflot library.

I am quite happy with the output from jsflot and it was very easy to use, many good tutorials.  I could have used Python instead of PHP, but I did a bit of PHP stuff recently, so it was easier for me to install php5 rather than learning Python (enough other stuff to learn in getting up to speed with the Yun).  I might eventually use a MySQL database instead of csv files, but for now it does the job.


AlexandreB, thanks for sharing.

I took a slightly different approach to essentially the same scenario.  I am developing a controller for my greenhouse and I want to log the data every five minutes.  So my steps are:

1. Every five minutes the Arduino collects the sensor data and adds a new line in a csv file in a dedicated directory on the SD card.

I'm working on something similar and I would like to give a look to your code to write a csv



Here is my sketch as it stands right now, not very clean code as I am still experimenting.  I basically generate the new line in the csv file in the routines 'checkTemperatures' and 'logEntry'.

Code: [Select]

  Controls the Green House
  This version samples the temperature every five minutes and stores
  the result on the SD card.
  The data can be retrieved from the SD card via the web interface.
  created 26 October 2013
  by Bjarne B. Christensen
  This code is in the public domain

#include <Bridge.h>
#include <FileIO.h>
#include <Process.h>
#include <DS18B20.h>
#include "SensorAddresses.h"
#include "EmailAccount.h"  // contains sending email account information
#include <X10Firecracker.h>

#define RTS_PIN  4    // RTS line for CM17A - DB9 pin 7
#define DTR_PIN  3    // DTR line for CM17A - DB9 pin 4
                      // GND line for CM17A - DB9 pin 5
#define BIT_DELAY 1    // mS delay between bits (1 mS OK)

#define HEAT_LAMP 4   // Greenhouse Heat Lamp
#define LIGHTS    5   // Greenhouse Lights

#define good_strength 25

Process time;
Process wifiCheck;  // process used to get the wifi status
String minuteString;
String timeString;
String dateString;
String nowMinuteString = "";
float boardTemperature;
float outdoorTemperature;
float indoorTemperature;
int voltagePin = A0;
int voltageValue = 0;
float inputVoltage = 0;
char charBuf[40];
int nextMinute = 0;
int minute;
boolean armEmail = true;
int tries;
int test = 0;
int led = 13;   // LED on pin 13
String ledTest;
byte previous_state = 0;
int wifiLevel;

DS18B20 ds(10);

void setup() {
   pinMode(led, OUTPUT);

   // Bridge startup
   digitalWrite(13, LOW);
   digitalWrite(13, HIGH);


   nextMinute = (minute - minute%5) + 5;
   if(nextMinute > 59) nextMinute -= 60;


   String message = "Greenhouse starting at ";
   message += timeString;
   sendEmail("Start message", message);

   X10.sendCmd(hcC, LIGHTS, cmdOff);
   X10.sendCmd(hcC, HEAT_LAMP, cmdOff);

void loop() {
   ledTest = timeString.substring(7);
   if((ledTest == "0") || (ledTest == "2") || (ledTest == "4") || (ledTest == "6") || (ledTest == "8")) {
      digitalWrite(led, 1);
   } else {
      digitalWrite(led, 0);

   if(timeString.substring(0,5) == "00:00") {
      if(armEmail) {
         String message = "Greenhouse daily log for ";
         message += dateString;
         sendEmail("Greenhouse message", message);
         armEmail = false;
         String file = dateString;
         file += ".csv";
         message = "Time,Voltage,Board,Outside,Inside,";
         message += dateString;
         logEntry(file, message);
   } else {
      armEmail = true;
   if(minute == nextMinute) {
      nextMinute = minute + 5;
      if(nextMinute > 59) nextMinute -= 60;
      if(test == 0) {
        X10.sendCmd(hcC, HEAT_LAMP, cmdOn);
        X10.sendCmd(hcC, LIGHTS, cmdOff);
        X10.sendCmd(hcC, HEAT_LAMP, cmdOn);
        X10.sendCmd(hcC, LIGHTS, cmdOff);
        test = 1;
      } else {
        X10.sendCmd(hcC, HEAT_LAMP, cmdOff);
        X10.sendCmd(hcC, LIGHTS, cmdOn);
        X10.sendCmd(hcC, HEAT_LAMP, cmdOff);
        X10.sendCmd(hcC, LIGHTS, cmdOn);
        test = 0;

   delay(50); // Poll every 50ms

void checkTemperatures()
   String message = timeString.substring(0,5);
   message += ",";
   voltageValue = analogRead(voltagePin);
   //inputVoltage = (float (voltageValue * 40)) / 1024;
   inputVoltage = (float (voltageValue * 67)) / 2048;  // adjust for resistor values
   message += inputVoltage;
   message += ",";
   boardTemperature = ds.read(boardAddress);
   message += boardTemperature;
   message += ",";
   outdoorTemperature = ds.read(outdoorAddress);
   message += outdoorTemperature;
   message += ",";
   indoorTemperature = ds.read(indoorAddress);
   message += indoorTemperature;
   message += ",";
   message += wifiLevel;

   String file = dateString;
   file += ".csv";
   logEntry(file, message);

void getTime() {
   Process time;
   String tempString;
   boolean valid = false;
   tries = 0;
   while(!valid) {
      // get the time from the server:
      time.runShellCommand("date +\"%Y%m%d %T\"");
      tempString = "";
      while(time.available()) {
         char c = time.read();
         if(c != '\n')
            tempString += c;
      dateString = tempString.substring(0, 4) + "-" + tempString.substring(4, 6) + "-" + tempString.substring(6,8);
      if(dateString != "2013-10-20") valid = true;
   timeString = tempString.substring(9);
   minuteString = tempString.substring(12,14);

   minuteString.toCharArray(charBuf, 30);
   minute = atoi(charBuf);

void logEntry(String file, String message)
   String fileName;
   fileName = "/mnt/sd/data/greenhouse/";
   fileName += file;
   fileName.toCharArray(charBuf, 40);
   File dataFile = FileSystem.open(charBuf, FILE_APPEND);

void sendEmail(String subject, String message) {
  Process p;
  String dataString;

  dataString +="From: ";
  dataString +=GMAIL_USER_NAME;
  dataString +="\nTo: ";
  dataString +=TO_EMAIL_ADDRESS;
  dataString +="\nSubject: ";
  dataString +=subject;
  dataString +="\n\n";
  dataString +=message;
  dataString +="\n";
  File dataFile = FileSystem.open("/mnt/sd/data/mail.txt", FILE_WRITE);
  // if the file is available, write to it and send email
  if (dataFile) {
    dataString = "cat /mnt/sd/data/mail.txt | ssmtp ";
    dataString += TO_EMAIL_ADDRESS;
    dataString += "  2>&1";
  else {
    Serial.println("error opening datalog.txt");

void checkwifistatus(void)
   wifiCheck.runShellCommand("/usr/bin/pretty-wifi-info.lua | grep Signal"); // grab just the signal data
   String strength = wifiCheck.readString();
   byte totallength = strength.length();
   strength = strength.substring(8, totallength-1);
   int level = strength.toInt();

   wifiLevel = level;




3. I am invoking a PHP file from the web server, which parses the data and plots it using the jsflot library.

I am quite happy with the output from jsflot and it was very easy to use, many good tutorials.

The jsflot library seems interesting but when googling, I found a server technology. Are you using Flotr (which is a javascript client technology). If you are using jsflot either I don't look at the right library or I do not have understand correctly your use case.
You seem happy with this lib. Could you tell me more about it?


I think I misspoke when I said jsflot, I should have said JQuery Flot, sorry.  Here is the URL:


You are right that it is javascript, so client side execution of the plot, while the data is pulled on the server side.

I am quite happy with how easy it was to create a plot that I like in my browser.  There are some nice examples on the website.

For reference, here is my index.html and Temp.php code:

Code: [Select]
<!DOCTYPE html>
  <meta http-equiv="Refresh" content="0; url=Temp.php">

Code: [Select]

= array();
$board = array();
$outdoor = array();
$indoor = array();
$diff = array();
$wifi = array();
$outdoorMax = -100;
$outdoorMin 100;
$indoorMax = -100;
$indoorMin 100;

$directory opendir('/mnt/sd/data/greenhouse/');
while (
false !== ($entry readdir($directory)))
$fileindex sizeof($files) - 1;

$day $_GET['day'];
$day != "") && ($day 0))
$fileindex = (sizeof($files) - 1) - $day;
   if (
$fileindex 2$fileindex 2;

$day substr($files[$fileindex], 010);

$timeStamp0 strtotime($day." UTC");

$file fopen("/mnt/sd/data/greenhouse/".$day.".csv""r");
$index 0;
while ((
$data fgetcsv($file1000",")) !== FALSE
$dataTime $data[0];
$voltage $data[1];
$boardTemp round((($data[2] * 1.8) + 32.0), 2);
$outsideTemp round((($data[3] * 1.8) + 32.0), 2);
$insideTemp round((($data[4] * 1.8) + 32.0), 2);
$wifiStrength $data[5];
$wifiStrength 20$wifiStrength 20;
$diffTemp $insideTemp $outsideTemp;
   if (
$time = ((substr($data[0], 02) * 60) + substr($data[0], 3)) * 60;
$timeStamp = ($timeStamp0 $time)."000";
//echo $data[0]."  ".$timeStamp."   ".$time."<br>";
$tempArray = array($timeStamp$boardTemp);
$tempArray = array($timeStamp$outsideTemp);
$tempArray = array($timeStamp$insideTemp);
$tempArray = array($timeStamp$diffTemp);
$tempArray = array($timeStamp$wifiStrength);
      if (
$outsideTemp $outdoorMax$outdoorMax $outsideTemp;
      if (
$outsideTemp $outdoorMin$outdoorMin $outsideTemp;
      if (
$insideTemp $indoorMax$indoorMax $insideTemp;
      if (
$insideTemp $indoorMin$indoorMin $insideTemp;
$day $data[5];
$index $index 1;
$js_indoor json_encode($indoor);
$js_outdoor json_encode($outdoor);
$js_board json_encode($board);
$js_diff json_encode($diff);
$js_wifi json_encode($wifi);
$html = <<<EOD
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <title>Greenhouse Temperatures</title>
    <script src="../js/jquery.js" type='text/javascript'></script> 
    <script type="text/javascript" src="../js/jquery.flot.min.js"></script>
    <script type="text/javascript" src="../js/jquery.flot.time.js"></script>   
    <script type="text/javascript" src="../js/jquery.flot.symbol.js"></script>
    <script type="text/javascript" src="../js/jquery.flot.axislabels.js"></script>
    <script type="text/javascript" src="../js/jshashtable-3.0.js"></script>   
    <script type="text/javascript" src="../js/jquery.numberformatter-1.2.4.min.js"></script>
        var data1 = 
        var data2 = 
        var data3 = 
        var data4 = 
        var data5 = 
        var dataset1 = [
            //{ label: "Board", data: data3, color: "red", points: { symbol: "square"} },
            { label: "WiFi", data: data5, color: "yellow" },
            { label: "Difference", data: data4, color: "brown", yaxis: 2 },
            { label: "Outdoor", data: data2, color: "blue", points: { symbol: "diamond"} },
            { label: "Indoor", data: data1, color: "green", points: { symbol: "triangle"} }

        var options1 = {
            series: {
                lines: {
                    show: true
                points: {
                    radius: 2,
                    fill: true,
                    show: true
            xaxis: {
                mode: "time",
                timeformat: "%H:%M",
                tickSize: [1, "hour"],
                //tickLength: 0,
                axisLabel: "
                axisLabelUseCanvas: true,
                axisLabelFontSizePixels: 14,
                axisLabelFontFamily: 'Verdana, Arial',
                axisLabelPadding: 10
            yaxes: [{
                //position: "right",
                axisLabel: "Temperature (F) (WiFi %)",
                min: 20,
                max: 100,
                tickSize: 5,
                axisLabelUseCanvas: true,
                axisLabelFontSizePixels: 14,
                axisLabelFontFamily: 'Verdana, Arial',
                axisLabelPadding: 3,
                tickFormatter: function (v, axis) {
                    return $.formatNumber(v, { format: "#,###.#", locale: "us" });
            }, {
                position: "right",
                axisLabel: "Difference (F)",
                min: -2,
                max: 30,
                tickSize: 2,
                axisLabelUseCanvas: true,
                axisLabelFontSizePixels: 14,
                axisLabelFontFamily: 'Verdana, Arial',
                axisLabelPadding: 3,
                tickFormatter: function (v, axis) {
                    return $.formatNumber(v, { format: "#,###.#", locale: "us" });
            legend: {
                noColumns: 0,
                labelBoxBorderColor: "#000000",
                position: "nw"
            grid: {
                hoverable: true,
                borderWidth: 2,
                borderColor: "#633200",
                backgroundColor: { colors: ["#ffffff", "#EDF5FF"] }
            colors: ["#FF0000", "#0022FF"]
        $(document).ready(function () {
            $.plot($("#flot-placeholder1"), dataset1, options1);
        var previousPoint = null, previousLabel = null;

The post exceeded 9500 characters, so I had to split Temp.php, the rest is in the next post.


Last part of Temp.php:
Code: [Select]
        $.fn.UseTooltip = function () {
            $(this).bind("plothover", function (event, pos, item) {
                if (item) {
                    if ((previousLabel != item.series.label) || (previousPoint != item.dataIndex)) {
                        previousPoint = item.dataIndex;
                        previousLabel = item.series.label;

                        var x = item.datapoint[0];
                        var y = item.datapoint[1];
                        var datepoint = new Date(x + 28800000);  // get rid of -8 hour offset
                        if (datepoint.getMinutes() < 10)
                           var timeString = datepoint.getHours() + ":" + "0" + datepoint.getMinutes();
                           var timeString = datepoint.getHours() + ":" + datepoint.getMinutes();

                        var color = item.series.color;
                        var month = new Date(x).getMonth();


                        if (item.seriesIndex == 0) {
                            "<strong>" + item.series.label + "</strong><br>" + timeString + " : <strong>" + y + "</strong>(F)");
                        } else {
                            "<strong>" + item.series.label + "</strong><br>" + timeString + " : <strong>" + y + "</strong>(F)");
                } else {
                    previousPoint = null;

        function showTooltip(x, y, color, contents) {
            $('<div id="tooltip">' + contents + '</div>').css({
                position: 'absolute',
                display: 'none',
                top: y - 40,
                left: x - 40,
                border: '2px solid ' + color,
                padding: '3px',
                'font-size': '9px',
                'border-radius': '5px',
                'background-color': '#fff',
                'font-family': 'Verdana, Arial, Helvetica, Tahoma, sans-serif',
                opacity: 0.9
    <div style="width:100%;height:540px;text-align:center;margin:10px">       
        <div id="flot-placeholder1" style="width:98%;height:100%;"></div>       

$button1 = "Previus Day";
$value1 =  sizeof($files) - $fileindex;
$button2 = "Next Day";
$value2 =  sizeof($files) - $fileindex - 2;
if($value2 < 0) $value2 = 0;

$html = $html . <<<EOD
   <form name="input" action="Temp.php" method="get"
           style="position:absolute; top:5.8in; left:0.5in">
   <input type="hidden" name="day" value=$value1>
   <button type="submit" style="width:1.3in; height:0.5in; font-size:12pt">$button1</button>

   <form name="input" action="Temp.php" method="get"
           style="position:absolute; top:5.8in; left:2.0in">
   <input type="hidden" name="day" value=$value2>
   <button type="submit" style="width:1.3in; height:0.5in; font-size:12pt">$button2</button>
  <p style="position:absolute; top:5.65in; left:3.6in; font-size:12pt"><b>Inside Temp:</b></p>
  <p style="position:absolute; top:5.65in; left:4.7in; font-size:12pt"><b>Now:</b> $insideTemp <b>Min:</b> $indoorMin <b>Max:</b> $indoorMax</p>

  <p style="position:absolute; top:5.95in; left:3.6in; font-size:12pt"><b>Outside Temp:</b></p>
  <p style="position:absolute; top:5.95in; left:4.7in; font-size:12pt"><b>Now:</b> $outsideTemp <b>Min:</b> $outdoorMin <b>Max:</b> $outdoorMax</p>

  <p style="position:absolute; top:5.65in; left:7.4in; font-size:12pt"><b>Last Sample Time:</b> $dataTime</p>

  <p style="position:absolute; top:5.95in; left:7.4in; font-size:12pt"><b>Board Temp:</b> $boardTemp</p>

  <p style="position:absolute; top:5.95in; left:8.9in; font-size:12pt"><b>Voltage:</b> $voltage V</p>


echo $html;




Here is my sketch as it stands right now, not very clean code as I am still experimenting.  I basically generate the new line in the csv file in the routines 'checkTemperatures' and 'logEntry'.


Thanks a lot I've got a lot to learn from your code ...
but i didn't figure how to plot my data jet ...it's to complicate for me at the moment
I'll bore you another time if you want


I've found something interesting to plot data easily
just in case give a look


Thank you raxpa and bjarne for the two links on dygraphs and flowcharts.

One more tool that can be used : gnuplot
Thanks to a python script, I produce a data file for gnuplot. Then I call gnuplot with this gnuplot script to produce an svg file :
Code: [Select]

set encoding iso_8859_1
set terminal svg fsize 10 mouse jsdir "http://gnuplot.sourceforge.net/demo_svg_4.6/"
set xlabel "Date"
set xdata time
set ylabel "°C"
set timefmt "%s"
set format x "%H:%M"
set title "Cuisine"
set linetype 1 lw 1 lc rgb "blue" pointtype 6 pointsize 0.5
plot 'kitchen.dat' using 1:2 with linespoints title "cuisine"

My python script read the result of a rrdtool xport command.
Code: [Select]

# -*- coding: utf-8 -*-
Convertit la sortie de la rrdtool xport
en quelque chose de bien pour gnuplot
import xml.etree.ElementTree as ET
tree = ET.parse('/mnt/sda1/arduino/rrd/xport131208.xml')

lignes = list(tree.iter('row'))

lignes_propres = filter(lambda e: (e[1].text != 'NaN'), lignes)

datalist = map(lambda e: (e[0].text, e[1].text), lignes_propres)
f = open('kitchen.dat', 'w+')

map(lambda e: f.write(e[0].text + " " + e[1].text + "\n"), lignes_propres )


You need to have installed gnuplot and to process the xml python-expat packages.
Has a result you have an interactive svg file to be embedded in an html file served by the Yun. The displaying of the svg file by the browser is anti aliased and so better than a png file.

Arduino Yun is Gnuplot capable and it is nice.



Merci / Thanks to You all , that helped me a lot :)


May 20, 2015, 02:48 pm Last Edit: May 20, 2015, 02:49 pm by sonnyyu
ShapeShifter has a super clean way to do it.

Displaying it this way is MUCH easier than writing web pages to call rrdcgi.


Go Up