1

I am using Pyserial and PyQtgraph to plot live data. The connection between the device that i am reading the data from (an arduino) and my pc works fine, i mean that i can read the data. The problem comes that when I disconnect the device, data is still drawing in the plot. And, if I let it keep reading, after a while, the plot crashes and i have to start over again.

I was reading some posts and I have found this:

implementing pyqtgraph for live data graphing

So, i think the problem is that in my code, the data is append to a list and then is plot, and that makes it slow and maybe that is why it crashes.

This is my code:

class MyApplication(QtGui.QApplication):
  def __init__(self, *args, **kwargs):
    super(MyApplication, self).__init__(*args, **kwargs)
    self.t = QTime()
    self.t.start()

    self.data = deque()

    self.cnt = 0 

    self.win = pg.GraphicsWindow()

    self.plot = self.win.addPlot(title='Timed data')
    self.curve = self.plot.plot()

    self.tmr = QTimer()
    self.tmr.timeout.connect(self.update)
    self.tmr.start(100)

    self.cnt = 0

    print "Opening port"
    self.raw=serial.Serial("com4",9600)
    print "Port is open"

  def update(self):
    line = self.raw.read()
    ardString = map(ord, line)
    for number in ardString:
        numb = float(number/77.57)
        self.cnt += 1
        x = self.cnt/20
        
        self.data.append({'x': x , 'y': numb}) 
        x = [item['x'] for item in self.data]
        y = [item['y'] for item in self.data]
        self.curve.setData(x=x, y=y)

How can i modify my code to use the code written in the post of above? Or how can I draw the data that is coming without append it to a list?

Sorry, but i am new with PyQtGraph and I am a confused right now. HOpe you can help me.

---------- EDIT ---------

I´ve tried a simpler code like this one:

import serial
import numpy
import matplotlib.pyplot as plt

print "Opening port"
port = "com4"
arduinoData = serial.Serial(port, 9600)

while True:
  if arduinoData.inWaiting()>0:
    print "Reading data"
    arduinoString = arduinoData.read(arduinoData.inWaiting())

    bytes = map(ord, arduinoString)

    for byte in bytes:
        print byte
  else:
    print "There is no data"

So, after it shows the data in the command prompt, I disconnect the device, and I can see that the data are still showing for a few seconds. Then, the "There is no data" text appears. So, what could be the problem? I know that, it is buffered data, but it seems to me that it is the same that is happening with the other code.

---------- EDIT 2 ---------

I finally accomplish to do what i need. Thank you @busfault for all your help and patience.

This is the code for the update method:

def update(self): 
    line = self.raw.read([1])                        
    ardString = map(ord, line)                        
                                                      
    for number in ardString:                          
        numb = float(number/77.57)                    

        self.data.append(numb)                        
        self.yData.append(numb)                       
                                                      
        if len (self.yData)>300 :
            self.yData = []
            self.raw.flush()
        
    self.curve.setData(self.yData)

What i do now is that the data goes to two different lists: self.yData and self.data. In self.yData I can only append up to 300 data items(this is random, I could have chosen 500), and then I flush all the data and "clear" the list to start again.

Whit this I can see live data with no delay and save all of them in another place safe.

Community
  • 1
  • 1
Pablo Flores
  • 667
  • 1
  • 13
  • 33
  • Do you need to show the entire data-set? – Tom Myddeltyn May 09 '16 at 20:58
  • Hi , thank foro your reply. Yes, i need the entire data set – Pablo Flores May 09 '16 at 21:39
  • Where exactly is the code crashing? I am assuming that it is during the `setData` call? Also, Do you need to store the data as you are? you could easily just have an `x` and a `y` list that you append to, plus. I think you may want to move the lines `x = [item['x'] for item in self.data]`, `y = [item['y'] for item in self.data]`, and `self.curve.setData(x=x, y=y)` Out one level so that they aren't called every time during the loop and only after the loop processes the `ardString` – Tom Myddeltyn May 10 '16 at 11:11

2 Answers2

1

I think if you create lists as you go you should see a speed up, if you are set on using the deque, then I would suggest moving the generation of x and y lists outside of the for loop since that is probably where you are spending a LOT of time when it may not be necessary.

def __init__(self, *args, **kwargs):
    super(MyApplication, self).__init__(*args, **kwargs)
    self.t = QTime()
    self.t.start()

    #self.data = deque()
    self.xValues = []
    self.yValues = []


    self.cnt = 0 

    self.win = pg.GraphicsWindow()

    self.plot = self.win.addPlot(title='Timed data')
    self.curve = self.plot.plot()

    self.tmr = QTimer()
    self.tmr.timeout.connect(self.update)
    self.tmr.start(100)

    self.cnt = 0

    print "Opening port"
    ##EDIT CHANGED THIS LINE TO INCLUDE TIMEOUT
    self.raw=serial.Serial("com4",9600, timeout=0)
    print "Port is open"

  def update(self):
    line = self.raw.read()
    ardString = map(ord, line)
    for number in ardString:
        numb = float(number/77.57)
        self.cnt += 1
        x = self.cnt/20

        self.xValues.append(x)
        self.yValues.append(numb)
        #self.data.append({'x': x , 'y': numb}) 
    #x = [item['x'] for item in self.data]
    #y = [item['y'] for item in self.data]
    self.curve.setData(x=x, y=y)

From the PySerial Documentation: https://pythonhosted.org/pyserial/pyserial_api.html#serial.Serial.read

Read size bytes from the serial port. If a timeout is set it may return less characters as requested. With no timeout it will block until the requested number of bytes is read.

and from the Constructor

Possible values for the parameter timeout which controls the behavior of read():

  • timeout = None: wait forever / until requested number of bytes are received
  • timeout = 0: non-blocking mode, return immediately in any case, returning zero or more, up to the requested number of bytes
  • timeout = x: set timeout to x seconds (float allowed) returns immediately when the requested number of bytes are available, otherwise wait until the timeout expires and return all bytes that were received until then.

So by default (timeout=None), when that self.raw.read() gets executed and there is no data it tries to read one byte then waits forever. For a byte to be written.

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

I was thinking some more about why your code is crashing after you disconnect. I think I know why, self.tmr keeps generateing signals every 100ms and your slot (update) keeps getting called over and over and with that, self.raw.read() keeps getting called (I think?)

try changing your code in update() as such:

def update(self):
    self.tmr.stop()#Prevent the timer from entering again.
    line = self.raw.read()
    ardString = map(ord, line)
    for number in ardString:
        numb = float(number/77.57)
        self.cnt += 1
        x = self.cnt/20

        self.xValues.append(x)
        self.yValues.append(numb)
        #self.data.append({'x': x , 'y': numb}) 
    #x = [item['x'] for item in self.data]
    #y = [item['y'] for item in self.data]
    self.curve.setData(x=x, y=y)
    self.tmr.start()#restart the timer (resets the timeout)

I don't know if it is critical to keep the 100mSec pulse though? If so you could use a Lock so that when update is called again it doesn't run the same code again. https://docs.python.org/2/library/threading.html#rlock-objects

I think this example shows that it is pretty straight-forward to implement.

import threading

some_rlock = threading.RLock()

with some_rlock:
    print "some_rlock is locked while this executes"
Community
  • 1
  • 1
Tom Myddeltyn
  • 1,307
  • 1
  • 13
  • 27
  • Hi, and thank you for your answer. I tried this before, but i keep getting the same: it keeps drawing after i disconnect the device. – Pablo Flores May 10 '16 at 20:51
  • I think I may have a better Idea now of what is happening. You should check to see if update is every getting beyond line = self.raw.read() – Tom Myddeltyn May 10 '16 at 21:04
  • Another thing you can add is to read multiple bytes at once instead of one at a time. How frequently and how much data is coming across the serial line? You may need/want to up from 9600baud. – Tom Myddeltyn May 10 '16 at 21:21
  • Sorry for late reply. The baud rate that i am using is 115200 baud. I see what you mean, but i do not know how to read multiple bytes. And thank you for all your help. – Pablo Flores May 10 '16 at 22:46
  • if you pass `self.raw.read(bytes=1)` a number it will read that many bytes or time out. wrt to the baud. It looks like you are setting it up with 9600bps based on your code. I don't have experience with PySerial, however based on the documentation, I think you might be able to look at this: `self.raw.in_waiting` to see how many bytes are in the buffer to be read. then you could pass that value to the `read()` function. – Tom Myddeltyn May 11 '16 at 01:31
  • Hi. I printed `self.raw.inWainting()` to check how many bytes where buffered after 1 second. Sometimes it showed aprox. 3000, and in another times it showed 4000 aprox. – Pablo Flores May 11 '16 at 05:36
  • 1
    I made it!!! . Thank you for all your help and patience. Look above on "Edit2". Thank you again. – Pablo Flores May 11 '16 at 19:06
0

may i suggest the following: upload the data to a db (database).

this would involve however adding more code to your existing program =)

but this is easily achievable using any db: sqlight, couchdb, mongodb..etc etc

or maybe just create a file that tracks the processed values? I noticed that you are not using a list, but rather tuples to store your key:value pairs within it.

    self.data.append({'x': x , 'y': numb}) 
    x = [item['x'] for item in self.data]
    y = [item['y'] for item in self.data]
    self.curve.setData(x=x, y=y)

So for option number two

tracking_file = open("filename.txt", "w") #w indicates write
tracking_file.writelines(the data) #instead of appending to a tuple or list
track_file.close()

this sequence opens a file and writes data to it, for more info regarding input/output https://docs.python.org/2/tutorial/inputoutput.html

and subsequently, you can read the data from files you created, the files will not be deleted if your program crashes, and you will be able to resume from where your program left before crashing or disconnecting by opening the file in read mode, to check the last input and continue adding values...

glls
  • 2,325
  • 1
  • 22
  • 39
  • I would suggest changing the file open style to `with open("filename.txt", 'w') as tracking_file:` Also note that using 'w' will overwrite the file you could change it to `with open("filename.txt", 'a') as tracking_file:` and then just write the current value. If you call that one every time you will have an incremental time-sink to write the file (why write all the data every time?) – Tom Myddeltyn May 10 '16 at 11:07
  • Hi, thank you for your answer. Yes, this is a solution for the crash that i have sometimes, Thank you. But what i need to do is to draw the data that i am receiving from the device, as fast as it comes. – Pablo Flores May 10 '16 at 20:38
  • Rather than appending twice (2 different lists, one for x and another for y), you can write data as a whole. either using a database, upload the data as a dictionary, {x:y} key value,and/or to a file. additionally, retrieve the data using key:value , this should optimize the writing speed of your program. I believe that originally, you posted that the problem was that your program crashed and you have to restart again. please consider reformulating your original post so other uses can provide accurate solutions – glls May 11 '16 at 01:01
  • to optimize performance, it would be important to identify the bottleneck in your program and check for optimization. you might want to consider reading: https://wiki.python.org/moin/PythonSpeed/PerformanceTips you can look for a decorator to time your method and try to optimize it. – glls May 11 '16 at 01:41