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

Posted

技术标签:

【中文标题】即使设备断开连接,使用 PyQtGraph 的实时绘图仍在绘制【英文标题】:Live plot using PyQtGraph is drawing even with the device disconnected 【发布时间】:2016-08-29 22:19:26 【问题描述】:

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

我正在阅读一些帖子,我发现了这个:

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)

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

抱歉,我是 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 个),然后我刷新所有数据并“清除”列表以重新开始。

有了这个,我可以立即查看实时数据并将它们全部保存在另一个安全的地方。

【问题讨论】:

你需要显示整个数据集吗? 您好,感谢您的回复。是的,我需要整个数据集 代码到底在哪里崩溃了?我假设它是在setData 通话期间?另外,您是否需要按原样存储数据?您可以轻松地添加一个 x 和一个 y 列表,以及附加的列表。我认为您可能希望将行 x = [item['x'] for item in self.data]y = [item['y'] for item in self.data]self.curve.setData(x=x, y=y) 移出一层,这样它们就不会在循环期间每次都被调用,并且只有在循环处理 ardString 【参考方案1】:

我认为,如果您在创建列表的过程中会看到速度加快,如果您设置使用双端队列,那么我建议将 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

从串口读取 size 个字节。如果设置了超时,它可能会根据请求返回更少的字符。没有超时,它将阻塞,直到读取请求的字节数。

来自构造函数

控制 read() 行为的参数 timeout 的可能值:

超时 = 无:永远等待/直到收到请求的字节数 timeout = 0:非阻塞模式,任何情况下立即返回,返回零个或多个,最多返回请求的字节数 timeout = x:将超时设置为 x 秒(允许浮点数)当请求的字节数可用时立即返回,否则等到超时到期并返回在此之前接收到的所有字节。

所以默认情况下 (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 毫秒脉冲是否很重要?如果是这样,您可以使用 Lock 以便在再次调用 update 时不会再次运行相同的代码。 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"

【讨论】:

您好,感谢您的回答。我以前试过这个,但我还是一样:断开设备后它一直在画。 我想我现在可能对正在发生的事情有了更好的想法。您应该检查更新是否超出 line = self.raw.read() 您可以添加的另一件事是一次读取多个字节,而不是一次读取一个。串行线路传输的频率和数据量是多少?您可能需要/想要提高 9600 波特率。 抱歉回复晚了。我使用的波特率为 115200 波特。我明白你的意思,但我不知道如何读取多个字节。并感谢您的所有帮助。 我成功了!!! .感谢您的所有帮助和耐心。看上面的“Edit2”。再次感谢您。【参考方案2】:

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

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

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

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

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

此序列打开一个文件并向其中写入数据,以获取有关输入/输出的更多信息https://docs.python.org/2/tutorial/inputoutput.html

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

【讨论】:

我建议将文件打开样式更改为with open("filename.txt", 'w') as tracking_file: 另请注意,使用“w”会覆盖文件,您可以将其更改为with open("filename.txt", 'a') as tracking_file:,然后只写入当前值。如果您每次都调用它,那么您将有一个增量时间来写入文件(为什么每次都写入所有数据?) 您好,谢谢您的回答。是的,这是我有时遇到的崩溃的解决方案,谢谢。但我需要做的是尽可能快地提取我从设备接收到的数据。 而不是追加两次(2个不同的列表,一个用于x,另一个用于y),您可以将数据作为一个整体写入。使用数据库,将数据作为字典、x:y 键值和/或文件上传。此外,使用 key:value 检索数据,这将优化程序的编写速度。我相信最初,您发布的问题是您的程序崩溃了,您必须重新启动。请考虑重新编写您的原始帖子,以便其他用途可以提供准确的解决方案 要优化性能,识别程序中的瓶颈并检查优化非常重要。您可能需要考虑阅读:wiki.python.org/moin/PythonSpeed/PerformanceTips 您可以寻找一个装饰器来为您的方法计时并尝试对其进行优化。

以上是关于即使设备断开连接,使用 PyQtGraph 的实时绘图仍在绘制的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 pyqtgraph 和 QSerialPort 创建实时图?

使用 pyqtgraph 进行实时应用好不好? [复制]

使用 pyqtgraph 实时更新条形图

在 PyQt4 中使用 PyQtGraph 进行实时绘图

使用 PyQtGraph 读取实时数据而不附加数据

h2 离开锁定文件,即使只创建一个连接并立即断开连接