Thursday, April 13, 2017

Temperature and hygrometer with FlashAir + Arduino + Raspberry Pi (4) : Visualize on Raspberry Pi

Table of contents.
  1. Read values from sensor
  2. Read from SD card with iSDIO
  3. Send values from arduino to Raspbery Pi
  4. Visualize on Raspberry Pi

It is the final round!
The data sent from Arduino is received by Raspberry Pi and made visible by Java Script.

Source code is on GitHub.

The below is connection sequence.


Data collection API receives data from arduino.
Received data is written in a log file, and then, Java Script of a display page refers the data.

Web API works on Raspberry Pi

Make Web API with Apache and PHP.
Just install some packages.
pi@raspberrypi ~ $ sudo apt-get install apache2
pi@raspberrypi ~ $ sudo apt-get install php
pi@raspberrypi ~ $ sudo systemctl start apache2
After installation, files under /var/www/html/ can be accepted via HTTP.

Data collection API

Receive data from arduino via HTTP, and then, write it into a log file.

Strictly speaking, file lock, log rotation, and etc... should be considered for concurrent access and log grows big. But, in this case, Arduino's number is small, so I omit them.

[ Points ]
  • Write the time on the Raspberry Pi as the time stamp at measurement.
    Because, arduino can get only elapsed time from wake up.
  • Switch the log file for each location ID.
  • Do not write to the log when abnormal values are sent from Arduino.

First, make a folder to write log files.

pi@raspberrypi ~ $ mkdir /var/www/html/data/
pi@raspberrypi ~ $ chmod 777 /var/www/html/data/

Save the following PHP in /var/www/html/api.php.
When arduino send data to "http://{IP address}/api.php",
the data is stored in /var/www/data/log_{LocationID}.txt.

OK

Display page

Show a bubble chart with Chart.js.

Put a HTML.
It just reads Java Script files and set a canvas.
Contents of canvas will be drawn by "mychart.js".







Temperature



Next, let's make mychart.js.

In the main method called by onLoad, a list of log files is created and passed to the drawing function.
Call it with setInterval every 10 minutes and redraw it.

The readyChart method defines parts that do not change, such as the bubble chart's coordinate axes and legends.
Please refer the manual of Chart.js.

function main() {

  var filePath = [
        './data/log_1.txt?'+new Date().getTime(),
        './data/log_2.txt?'+new Date().getTime(),
        './data/log_3.txt?'+new Date().getTime(),
        './data/log_4.txt?'+new Date().getTime(),
        './data/log_5.txt?'+new Date().getTime(),
        './data/log_6.txt?'+new Date().getTime()
        ];

  var myChart = readyChart();

  updateChart(filePath, myChart,0);

  setInterval(updateChart(filePath,myChart),60000);
}
function readyChart() {
  var data = [{},{},{},{},{},{}] ;

  var ctx = document.getElementById("myChart").getContext("2d");
  var myChart = new Chart(ctx, {
    type: 'bubble',
    data: {
      datasets: [
        { 
          label: "リビング",
          data: data[0],
          borderColor: '#FF0000',
          borderWidth: 3,
          showLine: true
        },
             :(中略)
        {
          label: "洗面所収納",
          data: data[5],
          borderColor: '#666666',
          borderWidth: 3,
          showLine: true
        }


      ]
    },
    options: {
      scales: {
        xAxes: [{
          scaleLabel: {
            display: true,
            labelString: '温度 [℃]'
          }
        }],
        yAxes: [{
          scaleLabel: {
            display: true,
            labelString: '湿度 [%]'
          }
        }]
      }
    }
  });

  return myChart;
}

And then, draw chart.
Get log files one by one from the passed file list and convert them to JSON format.

If XMLHttpRequest is called consecutively, redrawing runs asynchronously and the display will collapse.
So, each log is read sequentially by calling recursively at the end of XMLHttpRequest 's onLoad.

function updateChart(filePath, myChart, idx) {

  if ( idx >= filePath.length ){ return; }
  var req = new XMLHttpRequest();
  req.open("GET", filePath[idx], true);
  req.onload = function() {
    var data = convertData(csv2Array(req.responseText));
    myChart.data.datasets[idx].data = data;
    myChart.update();
    updateChart(filePath, myChart, idx+1);
  }
  req.send(null);
}

function convertData(source) {
  var data = [];
  var max = 1;
  var buf = {};
  for (var row in source) {
    y = String(parseInt(parseFloat(source[row][1]) + 0.5));
    x = String(parseInt(parseFloat(source[row][2]) + 0.5));
    if ( buf[x] == undefined ){
      buf[x] = {};
      buf[x][y] = 1;
    } else if ( buf[x][y] == undefined ){
      buf[x][y] = 1;
    }else{ buf[x][y] ++; }

    if ( buf[x][y] > max ){ max = buf[x][y]; }
  };

  for (var i in buf) {
    for (var j in buf[i]) {
      data.push( { x:i, y:j, r:20*buf[i][j]/max } );
    }
  }
  return data;
}

function csv2Array(str) {
  var csvData = [];
  var lines = str.split("\n");
  for (var i = 0; i < lines.length; ++i) {
    var cells = lines[i].split(",");
    csvData.push(cells);
  }
  return csvData;
}

Finished!

When you access the display page on iPad etc., it is displayed as follows.

No comments:

Post a Comment