0
votes

I am new to python and tkinter... I am using Tkinter to display a gauge and receive the info through serial com. I have my GUI ready and now need to read the serial values.

The problem I am facing is that I am not being able to read continuously the serial COM. I came across self.after, but it still does not work. Basically it does not display any value on the console. Any idea what might be wrong?

This is the main code. I have another file meter.py that has the gauge design

import tkinter as tk
import meter as m
import serial

class Mainframe(tk.Frame):

   def __init__(self,master,*args,**kwargs):
      super(Mainframe,self).__init__(master,*args,**kwargs)

      self.meter = m.Meter(self,height = 400,width = 400)
      self.meter.setrange(20,90)
      self.meter.pack()

      tk.Scale(self,width = 15 ,from_ = 20, to = 90
      ,orient = tk.HORIZONTAL
      ,command = self.setmeter).pack()

      tk.Button(self,text = 'Quit',width = 15,command = master.destroy).pack()
      tk.Button(self,text = 'Zoom',width = 15).pack()

   def setmeter(self,value):
      value = int(value)
      self.meter.set(value)   

class App(tk.Tk):

    def __init__(self):
        super(App,self).__init__()

        self.title('Try Meter')

        Mainframe(self).pack()

    def serie():
        ser = serial.Serial('COM2', 2400, timeout=1)
        line = ser.readline()   # read a '\n' terminated line
        print (line)
        self.after(100,serie) 


App().mainloop()
2

2 Answers

1
votes

Got it working . This is the code

class Mainframe(tk.Frame):

    def __init__(self,master,*args,**kwargs):
        super(Mainframe,self).__init__(master,*args,**kwargs)

        self.meter = Meter(self,height = 400,width = 400)
        self.meter.setrange(20,90)
        self.meter.pack()

        tk.Scale(self, width = 15, from_ = 20, to = 90, orient = tk.HORIZONTAL, 
                 command = self.setmeter).pack()

        tk.Button(self,text = 'Quit',width = 15,command = master.destroy).pack()
        tk.Button(self,text = 'Zoom',width = 15).pack()

        self.setmeter(15)

    def setmeter(self,value):
        value = int(value)
        self.meter.set(value)

class App(tk.Tk):

    def __init__(self):
        super(App,self).__init__()
        self.title('My Meter')     
        Mainframe(self).pack() 

root = App()       

serBuffer =b"" 
aux=""

def readSerial():
    serBuffer=b""
    while True:
        c = ser.read() # attempt to read a character from Serial

        #was anything read?
        if len(c) == 0:
            #print("cero")
            break
        #else:
            #print (len(c))

        if c == b'\n' :
            #we have all the info
            aux = serBuffer.split(b" ")
            print (aux[0]," ",aux[1])

            miinstance= Mainframe()
            miinstance.setmeter(50)

            #root.update()

            serBuffer = "" # empty the buffer
        else:
            serBuffer+=c# add to the buffer

        #aux= str(serBuffer, 'utf-8')


    root.after(700, readSerial) # check serial again soon

root.after(1000, readSerial)

root.mainloop()

BTW, know I am struggling with the gauge update. When I receive a complete string (detected by \n) I want to pass that value to the widget to update the gauge . The function that sets the value is setmeter, but with my code

miinstance= Mainframe()
miinstance.setmeter(50) <--put this value as test

I get this error . The first line are the serial char received (which are right)

b'90'   b'0'
Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Program Files\Python 3.5\lib\tkinter\__init__.py", line 1535, in __call__
return self.func(*args)
  File "C:\Program Files\Python 3.5\lib\tkinter\__init__.py", line 582, in callit
    func(*args)
  File "C:/Users/MARTIN/Documents/Personal/phyton/Nuevo anemometer/read_serie_v2.py", line 258, in readSerial
    miinstance= Mainframe()
TypeError: __init__() missing 1 required positional argument: 'master'

I tried to put "master" as a parameter , but it says "master" is not defined.

The code that I have (originally got it form the web),has a scale in the GUI,

tk.Scale(self,width = 15 ,from_ = 20, to = 90
  ,orient = tk.HORIZONTAL
  ,command = self.setmeter).pack()

that when I move it, it updates the gauge, but I do not need that .The gauge should update with the serial info received .

Any idea what might be wrong?

0
votes

The reason why your code doesn't work as expected is that you never call the serie() method of the App class. If you modify your code to add this call, perhaps at the end of the __init__() method, you should see serial data being printed to the console.

However, handling serial IO and the user interface in the same thread isn't a good idea. If there isn't any data to read from the serial port, readline() will block for up to 1 second (the timeout value you set when you initialised the serial port). This will block the tk main loop, and your user interface will be completely unresponsive. In my opinion, the best way to fix this would be to use one of the asynchronous IO libraries. I like Twisted as it has built-in support for tk (example) and serial communication, but there are several other options. If this feels like overkill, you could also move the serial polling code to its own thread or process using the threading or multiprocessing modules respectively.

Update

To answer the second part of your question:

In readSerial() you create a new instance of Mainframe(), but I think what you actually want to do is use the instance you created in the __init__() method of the App class. You'll need to store this as an attribute, using code like self.main_frame = Mainframe(self), then in readSerial() you can access it using root.main_frame.setmeter(50).