1

I can capture data from serial device via pyserial, at this time I can only export data to text file, the text file has format like below, it's have 3 columns

>21 21 0 
>
>41 41 0.5
>
>73 73 1
>    
....
>2053 2053 5
>
>2084 2084 5.5
>
>2125 2125 6

Now I want to use matplotlib to generate live graph has 2 figure (x,y) x,y are second and third column, first comlumn,'>', and lines don't have data can be remove

thank folks!

============================

Update : today, after follow these guides from

http://www.blendedtechnologies.com/realtime-plot-of-arduino-serial-data-using-python/231 http://eli.thegreenplace.net/2008/08/01/matplotlib-with-wxpython-guis pyserial - How to read the last line sent from a serial device

now I can live plot with threading but eliben said that this Guis only plot single value each time, that lead to me the very big limitation, beacause my purpose is plotting 2 or 3 column, here is the code was modified from blendedtechnologies

Here is serial handler : from threading import Thread

import time
import serial

last_received = ''
def receiving(ser):
    global last_received
    buffer = ''
    while True:
        buffer = buffer + ser.read(ser.inWaiting())
        if '\n' in buffer:
            lines = buffer.split('\n') # Guaranteed to have at least 2 entries
            last_received = lines[-2]
            #If the Arduino sends lots of empty lines, you'll lose the
            #last filled line, so you could make the above statement conditional
            #like so: if lines[-2]: last_received = lines[-2]
            buffer = lines[-1]


class SerialData(object):
    def __init__(self, init=50):
        try:
            self.ser = ser = serial.Serial(
                port='/dev/ttyS0',
                baudrate=9600,
                bytesize=serial.EIGHTBITS,
                parity=serial.PARITY_NONE,
                stopbits=serial.STOPBITS_ONE,
                timeout=0.1,
                xonxoff=0,
                rtscts=0,
                interCharTimeout=None
            )
        except serial.serialutil.SerialException:
            #no serial connection
            self.ser = None
        else:
            Thread(target=receiving, args=(self.ser,)).start()

    def next(self):
        if not self.ser:
            return 100  #return anything so we can test when Arduino isn't connected
                #return a float value or try a few times until we get one
        for i in range(40):
            raw_line = last_received[1:].split(' ').pop(0)
            try:
                return float(raw_line.strip())
            except ValueError:
                print 'bogus data',raw_line
                time.sleep(.005)
        return 0.
    def __del__(self):
        if self.ser:
            self.ser.close()

if __name__=='__main__':
    s = SerialData()
    for i in range(500):
        time.sleep(.015)
        print s.next()

For me I modified this segment so it can grab my 1st column data

for i in range(40):
                raw_line = last_received[1:].split(' ').pop(0)
                try:
                    return float(raw_line.strip())
                except ValueError:
                    print 'bogus data',raw_line
                    time.sleep(.005)
            return 0.

and generate graph base on these function on the GUI file

from Arduino_Monitor import SerialData as DataGen
def __init__(self):
        wx.Frame.__init__(self, None, -1, self.title)

        self.datagen = DataGen()
        self.data = [self.datagen.next()]

................................................

def init_plot(self):
        self.dpi = 100
        self.fig = Figure((3.0, 3.0), dpi=self.dpi)

        self.axes = self.fig.add_subplot(111)
        self.axes.set_axis_bgcolor('black')
        self.axes.set_title('Arduino Serial Data', size=12)

        pylab.setp(self.axes.get_xticklabels(), fontsize=8)
        pylab.setp(self.axes.get_yticklabels(), fontsize=8)

        # plot the data as a line series, and save the reference 
        # to the plotted line series
        #
        self.plot_data = self.axes.plot(
            self.data, 
            linewidth=1,
            color=(1, 1, 0),
            )[0]

So my next question is how to realtime grab at least 2 column and passing 2 columns'data to the GUIs that it can generate graph with 2 axis.

self.plot_data.set_xdata(np.arange(len(self.data)))  #my 3rd column data
self.plot_data.set_ydata(np.array(self.data))        #my 2nd column data
Community
  • 1
  • 1
billyduc
  • 692
  • 2
  • 8
  • 15
  • 2
    So, what do you have so far? Let's break it down in a few steps. 1: can you extract the numbers from the text? 2: can you plot the numbers? 3: can you make the graph update with incoming data? --- Where do you get stuck? – Daan Nov 25 '11 at 08:00
  • Thank you Daan for make me clear, at this time, I only capture the number from the device, next step I really concern on how to extract the number in the format that I can make plot later, can you suggest me on how to do that? – billyduc Nov 28 '11 at 07:56
  • I currently use Ubuntu, pyserial, wxpython for this project – billyduc Nov 28 '11 at 09:54

3 Answers3

2

Well, this reads your string and converts the numbers to floats. I assume you'll be able to adapt this as needed.

import numpy as np
import pylab as plt

str = '''>21 21 0 
>
>41 41 0.5
>
>73 73 1
>
>2053 2053 5
>
>2084 2084 5.5
>
>2125 2125 6'''
nums = np.array([[float(n) for n in sub[1:].split(' ') if len(n)>0] for sub in str.splitlines() if len(sub)>1])

fig = plt.figure(0)
ax = plt.subplot(2,1,1)
ax.plot(nums[:,0], nums[:,1], 'k.')
ax = plt.subplot(2,1,2)
ax.plot(nums[:,0], nums[:,2], 'r+')
plt.show()
Daan
  • 2,049
  • 14
  • 21
  • Hi Daan, very nice, it work smooth, thank you so much, can you explain to me how you extract the data to nums array ? Can you show me how to update the plot in real time, thank you in advance – billyduc Nov 30 '11 at 02:38
  • now I understand how you extract the data to the array! Can you show me how to update the plot in real time when pyserial capturing the data, thank you in advance – billyduc Nov 30 '11 at 03:01
  • I think that has been covered extensively by other questions&answers on stackoverflow and the matplotlib cookbook. A quick search turns up several possible solutions. Good luck! – Daan Nov 30 '11 at 17:31
  • Hi Daan, you're so kind, I follow this question http://stackoverflow.com/questions/1093598/pyserial-how-to-read-last-line-sent-from-serial-device and now I can plot but just only 1 column, I prefer your example bcs. it can pass 2 value so can plot 2 column at same time, can you suggest me how to plot 2 column at the same with threading in realtime. – billyduc Dec 01 '11 at 10:31
  • Perhaps you can update your question with a minimal example of your code? It seems to me that if you have all the separate components working (getting the data, plotting some data, updating a graph) you should be able to combine those parts by yourself. If not, can you please show us where exactly in the process things go wrong? – Daan Dec 01 '11 at 11:16
  • hi Daan, wish you have a nice day, I've already updated my question, could you give me your comments and ideas, thank you so much – billyduc Dec 02 '11 at 03:44
  • I guess your SerialData object should return not just the first column, but also the other numbers you want. Could it be this: raw_line = last_received[1:].split(' ').pop(0) is the culprit? I guess if you don't pop(), you get all three numbers. – Daan Dec 02 '11 at 07:54
  • hi Daan, you're right, but I use the pop because the plot function can only accept value from 1 column,now the last step for me is how to make the plot function draw 2 column at same time! – billyduc Dec 05 '11 at 04:13
  • hi Dann, the SerialData Object return 3 column default for me, but I can not draw from 2 column I need to modify this function self.plot_data.set_xdata(np.arange(len(self.data))) self.plot_data.set_ydata(np.array(self.data)) but I don't know how to write code to isolate data from the serialdata return, could you give me some advise? – billyduc Dec 06 '11 at 08:10
  • if np.array(self.data) is an array of shape (3,N) then you could select a column via np.array(self.data)[0,:] (or [1,:], or [2,:]). – Daan Dec 06 '11 at 08:16
  • hi Dann, after these lines raw_line = last_received[1:] try: return float(raw_line.strip()) and self.plot_data.set_ydata(np.array(self.data)[0,:]) it return an Index error: too many indices honestly I really don't know what its data return ?_? the hardest part for me now is to seperate to your format – billyduc Dec 06 '11 at 08:28
  • What type is self.data? What shape is np.array(self.data)? Does self.data contain what you expect it to contain? How about you add a few print statements throughout your code and check what happens to your variables as your script runs? – Daan Dec 06 '11 at 08:37
  • self.data is in list type and it have only one value inside [0.0], np.array(self.data) also have one value [0.] I think because of the float() function in the return, I think it should be return the object not the list with one value – billyduc Dec 06 '11 at 08:47
  • Well, if there is only one value in self.data, then obviously you can't extract multiple values from it. Are you still using this: raw_line = last_received[1:].split(' ').pop(0)? Because I think that's where you are reducing your input to a single value. Can you get to a point where raw_line contains several values and not just one? – Daan Dec 06 '11 at 08:51
  • no I use only raw_line = last_received[1:] try: return float(raw_line.strip()) #return float(torque) except ValueError: print 'bogus data',raw_line and it return to me like this bogus data 361 361 0 – billyduc Dec 06 '11 at 08:53
  • how about raw_line = last_received[1:].split() (so you split your string at the spaces and get three strings) and then: return [float(k) for k in raw_line] (so you get the float for every string in your list of strings). – Daan Dec 06 '11 at 09:03
  • my god it return self.data [[-481.0, -481.0, 0.0 ]] – billyduc Dec 06 '11 at 09:11
1

Here you have an Eli Bendersky's example of how plotting data arriving from a serial port

joaquin
  • 82,968
  • 29
  • 138
  • 152
  • thank you joaquin, I'm reading that and will dig into the code, I use the wxpython for this project – billyduc Nov 28 '11 at 07:57
  • @billyduc I do not understand what you mean with "I use wxpython". Your question indicates matplotlib + pyserial. Is that right?. In case you are **also** using wxPython for your program GUI probably you know that you can embed the matplotlib figure in a wxPython window. Dont forget to upvote the answer if you found it useful. – joaquin Nov 28 '11 at 12:11
  • hi @joaquin, I really want to vote up, but I need more than 15 reputations, I just new to stackoverflow :D, btw thank you for your suggestion – billyduc Nov 29 '11 at 02:53
0

some time back I had the same problem. I wasted a lot of writing same ting over and over again. so I wrote a python package for it.

https://github.com/girish946/plot-cat

you just have to write the logic for obtaining the data from serial port.

the example is here: https://github.com/girish946/plot-cat/blob/master/examples/test-ser.py

girish946
  • 745
  • 9
  • 24