racpi
Posts: 33
Joined: Mon Dec 30, 2013 11:54 am
Location: australia

how to get a serial event to update a tkinter screen

Tue Jul 22, 2014 8:10 am

I'm trying to make a gui interface that can monitor,log and interact with some wireless arduino devices (a solar panel tracker and a pid controlled wood heater) . I ultimately want to run it on the pi but I offer this example code that I'm running on a pc for ease of experimention.
what I would like to achieve is to sample an incoming serial stream for a certain value and if found update the gui. I can achieve this by pressing the button on the gui but I would like the serial input update the screen too.

Code: Select all

#!/usr/bin/env python

import Queue
import serial
import datetime
import os
import string
import sys 
import threading
import time

from Tkinter import *

count=0
ser=serial.Serial("com27",115200)

class App:
        
    def __init__(self, master):
        
       
        frame = Frame(master)
        frame.pack()
        self.desc=['none ','outmax ','kp','ki','kd','mkp','mki','mkd','no reg']
        
        self.reg   = StringVar()
        self.val   = StringVar()
        self.act   = StringVar()
        self.des   = StringVar()
        self.reg.set(0)
        self.val.set(0)
        self.act.set(0)
        self.des.set('blank')
        Label(frame, text='Reg').grid(row=0, column=0,sticky=W)
        Entry(frame, textvariable=self.reg).grid(row=0, column=1)
        Label(frame, text='Value').grid(row=1, column=0,sticky=W)
        Entry(frame, textvariable=self.val).grid(row=1, column=1)
        Label(frame, text='Action').grid(row=2, column=0,sticky=W)
        Entry(frame, textvariable=self.act).grid(row=2, column=1)
        Label(frame, text='desc').grid(row=3, column=0,sticky=W)
        
        Label(frame, textvariable=self.des).grid(row=3, column=1,sticky=W)
        button1 = Button(frame, text='xmit', command=self.up_load)
        
        button1.grid(row=4, column=1)
        
    def up_load(self):
       
        j = int(self.reg.get())
        t = chk_dig(j,2)
        t += chk_dig(int(self.val.get()),4)
        t += chk_dig(int(self.act.get()),1)
        t+="\r\n"
        #print t
        ser.write(t)
        tries=3
        time.sleep(.1)
        while tries:
            if ck.qsize()>0:
                nx=ck.get()
                if nx[0]=='R':
                    pk.put( nx)
                    self.reg.set(int(nx[1:3],16))
                    self.val.set(int(nx[3:7],16))
                    d=int(self.reg.get())
                    self.des.set(self.desc[d])
                    tries=1         
                else :
                    self.val.set(0)
                    self.des.set('nill response')
                    
            time.sleep(1)
            tries -= 1
             
        self.act.set(0) 



           
    
    
class cxthread (threading.Thread):
    def __init__(self,name):
        threading.Thread.__init__(self)
       
    def run(self):
        process_c(self.name)    


class mythread (threading.Thread):
    def __init__(self,name,q,c):
        threading.Thread.__init__(self)
        self.q=q
        self.c=c
    def run(self):
        process_d(self.name,self.q,self.c)

class pxthread (threading.Thread):
    def __init__(self,name,p):
        threading.Thread.__init__(self)
        self.q=p
    def run(self):
        process_p(self.name,self.q)

def process_d(tname,q,c):
    while exitflg==0:
        nx=ser.readline()
        #queueLock.acquire()
        if nx[0]=='R':
            while c.qsize()>0:
                tmp=c.get()
            c.put(nx)
             
        else:
            q.put(nx)
        time.sleep(.1)    
       
        #queueLock.release()
        
def process_p(tname,p):
   
    while exitflg==0:
         
         #print 'count ',count
         while p.qsize()>0:
            
                #queueLock.acquire()
                nx=p.get()
                #queueLock.release()
                
                pk.put('nx '+nx)
         time.sleep(.1)

def process_c(tname):
        global exitflg
        root = Tk()
        root.wm_title('device para')
        app = App(root)
        root.mainloop()
        exitflg=1
def chk_dig(x,n):
    j=hex(x).lstrip("0x")
    while len(j)<n:
         j="0"+j
         #print j
    return j 



        
#queueLock=threading.Lock()
wk=Queue.Queue(100)
ck=Queue.Queue(5)
pk=Queue.Queue(100)
exitflg=0
thread=mythread("rxd",wk,ck)

thread.start()

thread2=pxthread("pxd",wk)

thread2.start()

thread3=cxthread("cxd",)

thread3.start()




try:
    while exitflg ==0:
        if  pk.qsize()>0:
            nx=pk.get()
            print nx
        
        time.sleep(.1)

        
except  KeyboardInterrupt:
    exitflg=1
    print 'closing'
    thread.join()
    thread2.join()
    ser.close()            
    sys.exit()


TrevorAppleton
Posts: 74
Joined: Wed May 30, 2012 7:26 pm
Contact: Website

Re: how to get a serial event to update a tkinter screen

Tue Jul 22, 2014 2:56 pm

I have written something which does something similar in the past

You will need to have

Code: Select all

import threading
at the top.

You can then create a thread as such

Code: Select all

threading.Thread(target = nameOfFunction.start()
Then create a function which checks the incoming serial stream called nameOfFunction to suit.

Hope this helps you out.
Check out my blog post for Raspberry Pi and Python tutorials.

http://trevorappleton.blogspot.co.uk/

MadCow42
Posts: 106
Joined: Sun Jul 01, 2012 12:48 am

Re: how to get a serial event to update a tkinter screen

Thu Jul 24, 2014 6:01 pm

The threading solution posted above is the "right" way to do it, and will work fine. The challenge in doing this with a simple loop in Python is that once you call the Tkinter.mainloop() function, it basically is an endless loop on its own until you exit the widget. So - no code after your mainloop will execute until you kill the GUI.

Conversely, if you do the serial checking in a loop prior to your Tkinter.mainloop(), then the GUI will never execute.

However, there's another option - using the Tkinter.after() command. "After" allows you to have the GUI itself execute a function after a certain timeout. You can do that in a self-sustaining loop.

Pseudocode... (sorry, just from memory):

Code: Select all


def checkSerial():
    # check the serial for data and do something with it
    MainWindow.after(1, checkSerial) # to cause the loop.  You can pass in a reference to your Tk widget if you need to

# and then somewhere in your GUI code before the .mainloop, you need to call the checkSerial once to get it started.
MainWindow.after(1, checkSerial)


gkreidl
Posts: 6195
Joined: Thu Jan 26, 2012 1:07 pm
Location: Germany

Re: how to get a serial event to update a tkinter screen

Thu Jul 24, 2014 6:08 pm

And you can also use threads inside your Tkinter application, which in turn can send events to the main app.
Minimal Kiosk Browser (kweb)
Slim, fast webkit browser with support for audio+video+playlists+youtube+pdf+download
Optional fullscreen kiosk mode and command interface for embedded applications
Includes omxplayerGUI, an X front end for omxplayer

racpi
Posts: 33
Joined: Mon Dec 30, 2013 11:54 am
Location: australia

Re: how to get a serial event to update a tkinter screen

Fri Jul 25, 2014 10:58 am

thanks everyone for your replies.
I still don't understand the tk loop yet , when I try and insert a looping function in the tk loop it all goes wrong
if I insert this into my app class I get syntax errors (self has no function after I think)

Code: Select all

def checkSerial(self):
    # check the serial for data and do something with it
   self.after(1, checkSerial) # to cause the loop.  You can pass in a reference to your Tk widget if you need [code]
[/code]
I think running tkinter in a thread is not really the right way to go about this
I'm going to try running the other threads in the tk loop

scotty101
Posts: 3809
Joined: Fri Jun 08, 2012 6:03 pm

Re: how to get a serial event to update a tkinter screen

Fri Jul 25, 2014 12:49 pm

racpi wrote:if I insert this into my app class I get syntax errors (self has no function after I think)
Your class should inherit from one of the Tkinter classes which do have the .after method.

I created a traffic light example for someone several months back which shows how to use .after. Have a look at http://www.raspberrypi.org/forums/viewt ... 22#p453722
Electronic and Computer Engineer
Pi Interests: Home Automation, IOT, Python and Tkinter

MadCow42
Posts: 106
Joined: Sun Jul 01, 2012 12:48 am

Re: how to get a serial event to update a tkinter screen

Fri Jul 25, 2014 6:40 pm

scotty101 wrote:
racpi wrote:if I insert this into my app class I get syntax errors (self has no function after I think)
Your class should inherit from one of the Tkinter classes which do have the .after method.

I created a traffic light example for someone several months back which shows how to use .after. Have a look at http://www.raspberrypi.org/forums/viewt ... 22#p453722
Correct - any of the high-level Tkinter widgets have that method... as long as that's what you're passing in as the argument to "self" you should be fine.

See: http://effbot.org/tkinterbook/widget.htm

And look for the "after" method described there.

Return to “Python”