How can i get data without blocking?

﹥>﹥吖頭↗ 提交于 2019-12-13 08:38:37

问题


I have a serial port external device for getting data. I set two timers. One of them has to be for plotting(0.5sn) and the other for writing to a text file(15sn). Timers shouldn't get data from each other by list or array. Because sometimes I need to close plotting button.

So I have to get data from the same resource (coming continuous data), right? But when I try this, it blocked.

And how to get data without blocking? As an example the below code runs without blocking:

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

And it prints(as expected):

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

However my serial port doesn't work correctly like above:

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

and its output like those:

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''    

and (for commented two lines):

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'

As seen, whether t1 doesn't get data or t1 just get the next one. I also set timers (100ms), but same output. Could anybody guide me on this, what am I missing?


回答1:


You don't need to use 2 Timers to periodically save data. And you do not need to use Threads either. Just keep a counter variable in your main Frame class (or the class with the Timer and collecting the data -- your example is simple enough that it may not need splitting) and use that to determine when to write data. Also: keep the data read from the serial port in an array in the same class so you can plot it or save it or whatever else you might want:

in MyFrame1.__init__() add

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

Then in MyFrame1.timer1() do

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

Again, you do not necessarily need two timers or threads. You could do the plotting or the i/o in a separate thread, but you're going relatively slowly.

It would probably be wiser to eventually split up the code as "data collector" class with its own event loop and then transfer that to the GUI frame to plot as needed. But the example is small enough that such a refactoring is not needed yet.




回答2:


For non blocking operations with Gui / wxpython you need to use threads, you can use wx.lib.delayedresult

or you can use "threading" python module which i prefer

also if you need to get text data from a serial port you should decode them in utf-8, example:

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

also this is an example for reading serial port using threading you can use it as an idea of what you want to do

i added explanation comments in the code

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

Edit:

to communicate between your thread and main GUI i.e. you want to pass the serial data safely you should avoid using global variable and use queues instead, you can check this answer for how to use them



来源:https://stackoverflow.com/questions/55740673/how-can-i-get-data-without-blocking

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!