即使设备断开连接,使用 PyQtGraph 的实时绘图也在绘制

Live plot using PyQtGraph is drawing even with the device disconnected

我正在使用 Pyserial 和 PyQtgraph 绘制实时数据。我正在从(一个arduino)读取数据的设备和我的电脑之间的连接工作正常,我的意思是我可以读取数据。问题来了,当我断开设备连接时,数据仍在绘图中。而且,如果我让它一直读下去,过了一会儿,剧情就崩溃了,我不得不重新开始。

我正在阅读一些 posts 并且我发现了这个:

implementing pyqtgraph for live data graphing

所以,我认为问题是在我的代码中,数据被附加到一个列表,然后被绘制,这使得它变慢,也许这就是它崩溃的原因。

这是我的代码:

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)

如何修改我的代码以使用上面 post 中编写的代码?或者如何在不将数据附加到列表的情况下绘制即将到来的数据?

抱歉,我是 PyQtGraph 的新手,我现在很困惑。希望你能帮助我。

------------编辑--------

我试过像这样的更简单的代码:

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"

因此,在命令提示符中显示数据后,我断开设备,我可以看到数据仍在显示几秒钟。然后,出现“没有数据”文本。那么,可能是什么问题?我知道,它是缓冲数据,但在我看来,它与其他代码发生的情况相同。

------------ 编辑 2 ----------

我终于完成了我需要做的事情。感谢@busfault 的所有帮助和耐心。

这是 update 方法的代码:

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)

我现在所做的是将数据转到两个不同的列表:self.yDataself.data。 在 self.yData 中,我最多只能附加 300 个数据项(这是随机的,我本可以选择 500 个),然后我刷新所有数据并“清除”列表以重新开始。

有了这个我可以毫不延迟地看到实时数据并将它们全部保存在另一个安全的地方。

我可以提出以下建议:将数据上传到 db(数据库)。

然而,这将涉及向现有程序添加更多代码 =)

但这很容易使用任何数据库实现:sqlight、couchdb、mongodb..等等

或者只是创建一个文件来跟踪处理后的值? 我注意到您没有使用列表,而是使用元组在其中存储 key:value 对。

    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)

所以对于第二个选项

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()

此序列打开文件并向其中写入数据,以获取有关 input/output https://docs.python.org/2/tutorial/inputoutput.html

的更多信息

随后,您可以从您创建的文件中读取数据,如果您的程序崩溃,这些文件将不会被删除,您将能够通过打开文件从崩溃或断开连接之前程序离开的地方恢复读取模式,检查最后输入并继续添加值...

我认为如果你边创建列表你应该会看到一个加速,如果你设置使用双端队列,那么我建议将 x 和 y 列表的生成移到 for 循环之外,因为那是可能是您在没有必要的情况下花费大量时间的地方。

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)

来自 PySerial 文档: 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.

从构造函数

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.

所以默认情况下 (timeout=None),当 self.raw.read() 被执行并且没有数据时它会尝试读取一个字节然后永远等待。对于要写入的字节。

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

我在想更多关于为什么你的代码在你断开连接后崩溃的原因。我想我知道为什么,self.tmr 每隔 100 毫秒不断生成一次信号,并且您的插槽(更新)不断被调用,并且 self.raw.read() 不断被调用(我认为?)

尝试像这样更改 update() 中的代码:

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)

不过我不知道保持 100 毫秒脉冲是否重要?如果是这样,您可以使用锁,这样当再次调用更新时,它不会再次 运行 相同的代码。 https://docs.python.org/2/library/threading.html#rlock-objects

我认为这个例子表明实现起来非常简单。

import threading

some_rlock = threading.RLock()

with some_rlock:
    print "some_rlock is locked while this executes"