【问题标题】:How can i get data without blocking?如何在不阻塞的情况下获取数据?
【发布时间】:2019-04-18 07:00:15
【问题描述】:

我有一个用于获取数据的串行端口外部设备。我设置了两个定时器。其中一个必须用于绘图(0.5sn),另一个用于写入文本文件(15sn)。计时器不应通过列表或数组相互获取数据。因为有时我需要关闭绘图按钮。

所以我必须从同一个资源(即将到来的连续数据)中获取数据,对吗?但是当我尝试这个时,它被阻止了。

以及如何在不阻塞的情况下获取数据? 例如,下面的代码运行时没有阻塞:

# -*- coding: utf-8 -*- 
#!/usr/bin/python
import time
import numpy as np
import sys
import wx

def next_data():
    t0 = time.time()
    return t0

class MyFrame1 ( wx.Frame ):

    def __init__( self, parent ):
        wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = wx.EmptyString, pos = wx.DefaultPosition, size = wx.Size( 223,183 ), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL )

        self.SetSizeHints( wx.DefaultSize, wx.DefaultSize )

        bSizer1 = wx.BoxSizer( wx.VERTICAL )

        self.toggleBtn1 = wx.ToggleButton( self, wx.ID_ANY, u"Btn 1(enabled)", wx.DefaultPosition, wx.DefaultSize, 0 )
        bSizer1.Add( self.toggleBtn1, 1, wx.ALL|wx.EXPAND, 5 )

        self.toggleBtn2 = wx.ToggleButton( self, wx.ID_ANY, u"Btn 2(enabled)", wx.DefaultPosition, wx.DefaultSize, 0 )
        bSizer1.Add( self.toggleBtn2, 1, wx.ALL|wx.EXPAND, 5 )


        self.SetSizer( bSizer1 )
        self.Layout()
        self.timer1 = wx.Timer()
        self.timer1.SetOwner( self, 1 )
        self.timer1.Start( 1000 )

        self.timer2 = wx.Timer()
        self.timer2.SetOwner( self, 2 )
        self.timer2.Start( 1000 )


        self.Centre( wx.BOTH )

        # Connect Events
        self.toggleBtn1.Bind( wx.EVT_TOGGLEBUTTON, self.btn1_f )
        self.toggleBtn2.Bind( wx.EVT_TOGGLEBUTTON, self.btn2_f )
        self.Bind( wx.EVT_TIMER, self.timer1_f, id=1 )
        self.Bind( wx.EVT_TIMER, self.timer2_f, id=2 )

    def btn1_f( self, event ):
        event.Skip()

    def btn2_f( self, event ):
        event.Skip()

    def timer1_f( self, event ):
        t1 = next_data()
        print("t1     :    ",t1)

    def timer2_f( self, event ):
        t2  = next_data()
        print("t2*****:    ",t2)

app = wx.App()
f = MyFrame1(None)
f.Show(True)
app.MainLoop()

它会打印(如预期的那样):

t2*****:     1555568620.1363716
t1     :     1555568620.1363716
t2*****:     1555568621.1300163
t1     :     1555568621.1300163

但是我的串口不能像上面那样正常工作:

# -*- coding: utf-8 -*- 
#!/usr/bin/python
import time
import numpy as np
import sys
import wx
import serial

ser = serial.Serial('COM9',9600)

def next_data():
    #===========================================================================
    # for line in ser:       
    #     return line
    #===========================================================================
    data_str = ser.read(ser.inWaiting())  
    return data_str

## also the commented above 2 lines gave same output.


class MyFrame1 ( wx.Frame ):

    def __init__( self, parent ):
        wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = wx.EmptyString, pos = wx.DefaultPosition, size = wx.Size( 223,183 ), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL )

        self.SetSizeHints( wx.DefaultSize, wx.DefaultSize )

        bSizer1 = wx.BoxSizer( wx.VERTICAL )

        self.toggleBtn1 = wx.ToggleButton( self, wx.ID_ANY, u"Btn 1(enabled)", wx.DefaultPosition, wx.DefaultSize, 0 )
        bSizer1.Add( self.toggleBtn1, 1, wx.ALL|wx.EXPAND, 5 )

        self.toggleBtn2 = wx.ToggleButton( self, wx.ID_ANY, u"Btn 2(enabled)", wx.DefaultPosition, wx.DefaultSize, 0 )
        bSizer1.Add( self.toggleBtn2, 1, wx.ALL|wx.EXPAND, 5 )


        self.SetSizer( bSizer1 )
        self.Layout()
        self.timer1 = wx.Timer()
        self.timer1.SetOwner( self, 1 )
        self.timer1.Start( 1000 )

        self.timer2 = wx.Timer()
        self.timer2.SetOwner( self, 2 )
        self.timer2.Start( 1000 )


        self.Centre( wx.BOTH )

        # Connect Events
        self.toggleBtn1.Bind( wx.EVT_TOGGLEBUTTON, self.btn1_f )
        self.toggleBtn2.Bind( wx.EVT_TOGGLEBUTTON, self.btn2_f )
        self.Bind( wx.EVT_TIMER, self.timer1_f, id=1 )
        self.Bind( wx.EVT_TIMER, self.timer2_f, id=2 )

    def btn1_f( self, event ):
        event.Skip()

    def btn2_f( self, event ):
        event.Skip()

    def timer1_f( self, event ):
        t1 = next_data()
        print("t1     :    ",t1)

    def timer2_f( self, event ):
        t2 = next_data()
        print("t2*****:    ",t2)

app = wx.App()
f = MyFrame1(None)
f.Show(True)
app.MainLoop()

它的输出如下:

t2*****:     b'\xb5b\x010\x04\x018\>$GNRMC,063337.00.....$GNGGA...
t1     :     b''
t2*****:     b'\xb5b\x010\x04\x01\x18\>$GNRMC,063338.00.....$GNGGA...
t1     :     b''    

和(用于注释的两行):

t2*****:    b'$GPGSV,3,1,11,05,43,248,30,07,31,068,12,08,15,048,23,09,16,128,30*7B\r\n'
t1     :    b'$GPGSV,3,2,11,13,42,311,27,15,10,310,18,17,04,157,09,28,70,161,27*72\r\n'

正如所见,无论t1 没有获取数据还是t1 都只获取下一个。我还设置了计时器(100 毫秒),但输出相同。有人可以指导我吗,我错过了什么?

【问题讨论】:

  • 如果你执行“ser.read(ser.inWaiting())”你会得到缓冲区中的字符。之后缓冲区为空。数据现在仅在“data_str”中。你不能用 ser.read 再读一遍...!?
  • @dede,我该如何克服,有什么建议吗?
  • 我不太明白您要实现的目标.....您可以将 t1 声明为 self.t1(即 timer1_f 中的 self.t1=next_data())并设置 t2= timer2_f 中的 self.t1。

标签: python multithreading timer serial-port nonblocking


【解决方案1】:

您不需要使用 2 个计时器来定期保存数据。而且您也不需要使用线程。只需在您的主 Frame 类(或具有 Timer 并收集数据的类中保留一个计数器变量 - 您的示例很简单,它可能不需要拆分)并使用它来确定何时写入数据。另外:将从串行端口读取的数据保存在同一类的数组中,以便您可以绘制或保存它或您可能想要的任何其他内容:

MyFrame1.__init__()添加

self.last_saved_time = 0
self.plotting = True
self.data = []

然后在MyFrame1.timer1()

 # read and save data
 t1 = next_data()
 self.data.append(t1) # (or do more parsing, convert to numpy arrays, etc

 # send to plotting if it is enabled
 if self.plotting:  
      self.plot_data()

 # save if needed
 now = time.time()
 if (now - self.last_saved_time) > 15:
      self.save_data_to_file() 

同样,您不一定需要两个计时器或线程。您可以在单独的线程中进行绘图或 i/o,但您的速度相对较慢。

最终将代码拆分为具有自己的事件循环的“数据收集器”类,然后将其传输到 GUI 框架以根据需要进行绘图可能会更明智。但是这个例子足够小,还不需要这样的重构。

【讨论】:

  • 先生,您再次节省了我的时间。我真的很感谢您的考虑并花时间为我服务。非常感谢。
【解决方案2】:

对于 Gui / wxpython 的非阻塞操作,您需要使用线程, 你可以使用wx.lib.delayedresult

或者您可以使用我更喜欢的“线程”python 模块

另外,如果您需要从串口获取文本数据,您应该使用 utf-8 对其进行解码,例如:

ser = serial.Serial('COM9',9600)
try:
    line = ser.readline()
    line = line.decode("utf-8")
except UnicodeDecodeError:
    line = "\ncan't decode from serial, choose different baudrate\n"
    print(line)  # or do whatever you want with line

这也是一个使用线程读取串行端口的示例,您可以使用它来了解您想要做什么

我在代码中添加了解释cmets

from threading import Thread
from time import sleep

# you can define a class inherited from Thread class or simply use a function 
class ReadData(Thread):
    def __init__(self):
        super().__init__()
        self.running = True  # used as signal to kill the thread
        self.ser = serial.Serial('COM9',9600)  # open serial

    def run(self):  # run method need to be defined for threading to work
        while self.running:
            print('running')
            sleep(1)
        self.ser.close()

    def read(self):  # reading data from port
        if ser.is_open: 
            try:
                line = ser.readline()
                line = line.decode("utf-8")  # decode data 
            except UnicodeDecodeError:
                line = "\ncan't decode from serial, choose different baudrate\n"
                display(line)
                line = ""

    def kill(self):  # terminate thread safely
        self.running = False

read_data = ReadData()  # define new thread object
read_data.start()  # start the thread

sleep(5)

# in case you want to kill the running thread
print('killing running thread')
read_data.kill()

编辑:

要在您的线程和主 GUI 之间进行通信,即您希望安全地传递串行数据,您应该避免使用全局变量并改用队列,您可以查看 answer 以了解如何使用它们

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-09-09
    • 1970-01-01
    • 2020-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多