Monday, November 12, 2012

Real-Time Graphing of Arduino Data (Python, matplotlib, and PyQt)

A simple LED controlled by a photocell. adafruit.com
This is just going to be a quick example of how to read some serial data off of the arduino and make a real-time plot of that data using python (python 3.2), matplotlib, and PyQt. Nothing really fancy going on here and nothing that hasn't been done before (check the Resources) but this does combine a few disparate ideas and does get it working with python3, so that's something. Our test device is a simple LED hooked up to a photocell so that as it gets darker the LED gets brighter. The raw analog input from the photocell is written out to the serial port. On the python end, we simply read off the serial port and update a graph. All code is available on github.

Arduino

The arduino setup is fairly simple and mostly follows the tutorial supplied by adafruit.com listed under 'Simple Demonstration of Use'. Interfacing properly with the serial port, or, rather, getting python to correctly read the data from the serial port, did require altering a few things in the arduino code. Full source here.

The first thing to notice is our byte val = 0; at the top of the file. This is the variable that we are going to use to store the raw analog value off the arduino and I couldn't get it to work without it reading into a byte directly (as opposed to an int). Other than that, there is not much different from the adafruit tutorial except for the fact that I split the bulk of the code into a separate function adjust_led.

void loop(void) {
  val = analogRead(photocellPin);
  Serial.println(val,DEC);
  adjust_led(val);
  delay(100);
}

void adjust_led(int photocellReading){
  // LED gets brighter the darker it is at the sensor
  // that means we have to -invert- the reading from 0-1023 back to 1023-0
  photocellReading = 1023 - int(photocellReading);

  if(photocellReading < 0){
    photocellReading = 0;
  }
  if(photocellReading > 250){
    //now we have to map 0-1023 to 0-255 since thats the range analogWrite uses
    // for our purposes we only map 250-600 to get a better light range. 
    // feel free to experiment here.
    LEDbrightness = map(photocellReading, 250, 600, 0, 255);
  } else {
    LEDbrightness = 0;
  }

  analogWrite(LEDpin, LEDbrightness);
}

Pretty standard stuff. In the loop we read the analog pin and write it out to the serial port with a newline and using a DEC format. This is all that is needed in order to plot from python and the rest of the arduino code deals with adjusting the lighting on the LED. Two things to note: if the photocellReading is less than zero we bump it to zero; if less than 250 we don't turn on the light. So, ideally, the LED should stay off until it is "sufficiently" dark and then it should turn on and proceed to get brighter as it gets darker. The code is not perfect. For some reason I see some flickering and spiking going on and the overall transition isn't as smooth as I would like in terms of the visible effect of the LED. However, since our purpose is to get a graph going, and because looking at the graph might help us debug what is going on with our arduino, we just ignore all that for now and go on.

Python

There are two python files that we deal with. The first, SerialData.py, is fairly generic and should be able to use any serial data from an arduino. The second, light_sensor_plot.py, creates our PyQt gui, deals with our data, and creates the graph.

SerialData.py

The code is lifted almost directly from this tutorial (on his github this file is called Arduino_Monitor.py). A few differences:

        buffer = buffer + ser.read(ser.inWaiting()).decode()

I had to add .decode() while reading the buffer to get anything to work. I think this is a result of the difference in how python3 handles byte values versus python2.

        if not self.ser:
            return 0

I return a zero (0) instead of 100 if our serial port is not read. Ideally shouldn't even get here. I also remove the line that prints "bogus" on the ValueError exception. See my full code.

light_sensor_plot.py

Most of basics of this file are lifted from Chapter 6 of Sandro Tosi's Matplotlib for Python Developers. In that chapter you will find a section called 'Real-time update of a Matplotlib graph' which does what it says on the tin. In this example Tosi is graphing some cpu values from psutil and all I really do is strip all of that out, get our data from SerialData.py instead, and graph away. A lot of Tosi's code deals with the cpu values whereas for our data we don't need to do any additional processing. Perhaps the biggest difference is that Tosi is reading values once per second for a maximum of 30 seconds and sets up his graph accordingly. Our graph, however, wants updates more than once per second but also wants to scroll the graph accordingly. The update value is easy enough:

self.timer = self.startTimer(100)

This will give us an update every 100 ms, which should be smooth enough. The scrolling graph turns out to be just as easy:

        # force a redraw of the Figure - we start with an initial
        # horizontal axes but 'scroll' as time goes by
        if(self.cnt >= self.window_size):
            self.ax.set_xlim(self.cnt - self.window_size, self.cnt + 15)

So all we do here is check to see if our iteration count is greater than our window_size (30) and if so we set the x limit to be a 'window' that follows the data, with window_size space behind and 15 spaces ahead.

Other than that there is not actually much to the example. I think ideally I would like to split it up so that we have a file for reading from the arduino (currently SerialData.py), one for generating the gui, and one for dealing with the context specific data. You can see that our gui is really simple. Tosi and Eli go on to add fancy naivgation bars and all that so you can build from here.
Real-time graph of serial data values read from arduino.

Resources