How can the GUI interface pass messages to python via multi-threads? - python

I have a robotic project which invites the audience to type down sentences, then the robotic speaks and moves according to what the audience types. The problem is the GUI interface cannot pass the long sentences audience type, but only shorter sentences pass.
For now, there are two versions of codes, one version of code (test3.py) can interact with the speaking and movement of my robotics, but it has no GUI interface to interact. The other version of code has GUI interface, but it only passes very short sentences, not a comparatively longer sentences.
For now I have tried with two technicians who were testing from own computers, one works, one doesn't. I just don't know if there is any logic issue with the code with GUI interface within them. Here is the code for main3.py, which moves and speaks but having no GUI:
The code above works perfectly fine with the movement and the speaking, but it just does not work for the GUI interface. Here is the other version of code (main.py)which has GUI interface, but it cannot pass longer sentences to python to operate:
# Import Module
import tkinter as tk
from subprocess import call
# Create Object
window = tk.Tk()
# Set geometry
window.geometry("800x800")
def get_text():
\# Clear the output - This method is a dirty hack - fix when time !
usr_text = " "
label1 = tk.Label(window, text=usr_text)
canvas1.create_window(400, 460, window=label1)
# Get new input from user
usr_text = input_text.get()
\# Display user input to canvas
label1 = tk.Label(window, text=usr_text)
canvas1.create_window(400, 460, window=label1)
\# Call speak code - speak.py should be in the same folder
call(\["python3", "speak.py", usr_text\])
input_text.delete(0, 'end')
# -----------------------------------Main Code -----------------------------------
# Create GUI
canvas1 = tk.Canvas(window, width=800, height=600)
canvas1.pack()
input_text = tk.Entry(window)
canvas1.create_window(400, 280, window=input_text)
button1 = tk.Button(text='Enter your text', command=get_text)
canvas1.create_window(400, 360, window=button1)
window.mainloop()
```
----------------------------------
Accompaning this there is a speak2.py:
```
import sys
import pyttsx3 as tts
import nltk
import serial
from time import sleep
from threading import Thread
ser = serial.Serial(port='/dev/cu.usbmodem14101', baudrate = 9600) # Change this to the port
your arduino is connected to
\#ser = serial.Serial('/dev/cu.usbmodem2101', 9600)
arpabet = nltk.corpus.cmudict.dict()
def init_engine():
\# settings for Text to Speech (pyttsx3) library
speaking = tts.init()
speaking.setProperty('rate', 150) # setting up new voice rate: Increase to speed up |
decrease to slow down
voices = speaking.getProperty('voices') # getting details of current voice
speaking.setProperty('voice', voices\[0\].id) # changing index, changes voices. 1 for female
| o for male return speaking
# function - Text to speach
def say(all_words):
sleep(0.05)
speak.say(all_words)
speak.runAndWait() # blocks
# function - convert from words to phonemes and send to arduino/serial
def move_mouth(my_text):
array_length = len(results)
for x in range(0, array_length):
word_length = len(results\[x\]\[0\])
for y in range(0, word_length):
print(results\[x\]\[0\]\[y\], end='')
ser.write(results\[x\]\[0\]\[y\].encode())
print(".", end='')
ser.write('.'.encode())
print("$", end='')
ser.write('$'.encode())
print(" wrote to arduino")
# -----------------------------------Main Code -----------------------------------
results = \[\]`your text`
sentence = str(sys.argv\[1\]).lower()
print("You Typed:", sentence)
for words in sentence:
try:
results.append(arpabet\[words\])
except:
print('Spelling Error in text')
speak = init_engine()
# create two new threads
t1 = Thread(target=move_mouth(sentence))
t2 = Thread(target=say(sentence))
# start the threads
t2.start()
t1.start()
# wait for both threads to complete
t1.join()
t2.join()
I would really like to ask is there any logic issue with main.py or speak2.py codes? I would like to offer monetary payment if anyone could offer some help!
Looking forward to hearing any advice at this point!
Thank you very much in advance!
Chang

Related

Tkinter using mainloop and another loop

I'm doing a project where I read info from a socket and then intend to display it on a gui using tkinter. The thing is, my read info from socket is a loop and for the gui I need another loop.
I'm pretty inexperienced with both Python and Tkinter, which probably explains my mistake here.
The fd_dict is a dictionary with the properties and respective values of a car ex: gear, power, speed, etc (theme of my project).
The main problem is either I get the values from the socket or I display the gui, never both obviously, since it stays on the earlier loop.
while True:
# UDP server part of the connection
message, address = server_socket.recvfrom(1024)
del address
fdp = ForzaDataPacket(message)
fdp.wall_clock = dt.datetime.now()
# Get all properties
properties = fdp.get_props()
# Get parameters
data = fdp.to_list(params)
assert len(data) == len(properties)
# Zip into a dictionary
fd_dict = dict(zip(properties, data))
# Add timestamp
fd_dict['timestamp'] = str(fdp.wall_clock)
# Print of various testing values
print('GEAR: ', fd_dict['gear'])
print('SPEED(in KMH): ', fd_dict['speed'] * 3.6) #speed in kph
print('POWER(in HP): ', fd_dict['power'] * 0.0013596216173) #power in hp
#print('PERFORMANCE INDEX: ', fd_dict['car_performance_index'])
print('\n')
The tkinter code:
window = Tk()
window.title('Forza Horizon 5 Telemetry')
window.geometry("1500x800")
window.configure(bg="#1a1a1a")
frame = Frame(window)
frame.pack()
label_gear = Label(text = '0')
label_gear.configure(bg="darkgrey")
label_gear.pack()
I read about using after() and using classes, but I've never used them, and can't figure out how to apply them here.
Thanks in advance.

How to change the data output received externally in realtime within a program's text window?

I have been reluctant to ask this question - I have been trying to solve it and have looked everywhere!
So, I run a company where we have several third parties emailing us with notifications of sales - all to different addresses. Each email to us is exactly one sale. The issue is that nowhere in the office is it possible to see the current total of sales (submissions) for that moment. I have been dying to learn Python since I was young so I figured that I would find a cool way to graphically display a sort of ticker that has a big number on a screen that we can hang on a wall in the office for everyone to see, via a Raspberry Pi and an LCD. But my gawd is this tough to do.
Outside of Python, it's all good to go, I have figured out the emailing to one inbox and I have managed to set a timer on our Gmail to mark the reads after 24hrs. The problem I am having is that I really want the program to update its counting of the unreads in real-time automatically without scrolling forever down the screen.
I have looked everywhere, loops don't seem to refresh the number on the window, nothing works apart from restarting the program again which is not automated and requires myself to start the program again. So, please, any help on this would be great. I plan to add graphics later, and maybe a little sound that goes ping or CHaaaChing when a sale comes in, but I thought I'd get this hard stuff out of the way first.
Here is my code:
#! /usr/bin/env python3.4
import imaplib
import email
from tkinter import *
root = Tk()
def window(main):
main.title('submissions counter')
width = 500
height = 500
x = (main.winfo_screenwidth() //2 ) - (width // 2)
y = (main.winfo_screenheight() //2 ) - (height // 2)
main.geometry('{}x{}+{}+{}'.format(width, height, x, y))
mail=imaplib.IMAP4_SSL('imap.gmail.com',993)
mail.login('email#gmail.com','Password')
mail.select("Submissions")
typ, messageIDs = mail.search(None, "UNSEEN")
messageIDsString = str( messageIDs[0], encoding='utf8' )
listOfSplitStrings = messageIDsString.split(" ")
if len(listOfSplitStrings) == 0:
print('no submissions')
elif len(listOfSplitStrings) == 1:
print(len(listOfSplitStrings),'submission[s]')
else:
print(len(listOfSplitStrings),'submission[s]')
def main_content():
hello = Label(root, text=(len(listOfSplitStrings),'submission[s]'))
hello.pack()
window(root)
main_content()
root.mainloop()
Now I am trying with this code and getting nothing from it at all, no error messages, no results on the text box or on the command line...
#! /usr/bin/env python3.4
import imaplib
import email
import tkinter as tk
WIDTH = 500
HEIGHT = 500
def update():
mail=imaplib.IMAP4_SSL('imap.gmail.com',993)
mail.login('email"gmail.com','password')
mail.select("Submissions")
typ, messageIDs = mail.search(None, "UNSEEN")
messageIDsString = str( messageIDs[0], encoding='utf8' )
listOfSplitStrings = messageIDsString.split(" ")
number = len(listOfSplitStrings)
if number == 0:
info['text'] = 'no submissions'
else:
info['text'] = '{} submissions[s]'.format(number)
root.after(5000, update)
root = tk.Tk()
root.title('submissions counter')
x = (root.winfo_screenwidth()//2) - (WIDTH//2)
y = (root.winfo_screenheight()//2) - (HEIGHT//2)
root.geometry('{}x{}+{}+{}'.format(WIDTH, HEIGHT, x, y))
info = tk.Label(root, text='no submissions')
info.pack
update()
root.mainloop()
It seems you need root.after(milliseconds, function_name) to execute function periodically and not block mainloop() which redraws widgets in window.
I could look like this
#! /usr/bin/env python3.4
import imaplib
import email
import tkinter as tk
#import random # for test without using mail
# --- constants --- (UPPER_CASE_NAMES)
WIDTH = 500
HEIGHT = 500
# --- functions --- (lower_case_names)
def update():
mail = imaplib.IMAP4_SSL('imap.gmail.com', 993)
mail.login('email#gmail.com', 'Password')
mail.select("Submissions")
typ, messageIDs = mail.search(None, "UNSEEN")
messageIDsString = str(messageIDs[0], encoding='utf8')
listOfSplitStrings = messageIDsString.split(" ")
number = len(listOfSplitStrings)
#number = random.randint(0, 5) # for test without using mail
if number == 0:
#print('no submissions')
info['text'] = 'no submissions'
else:
#print(number, 'submission[s]')
info['text'] = '{} submission[s]'.format(number)
# update again after 5000ms (5seconds)
root.after(5000, update)
# --- main --- (lower_case_names)
root = tk.Tk()
root.title('submissions counter')
x = (root.winfo_screenwidth()//2) - (WIDTH//2)
y = (root.winfo_screenheight()//2) - (HEIGHT//2)
root.geometry('{}x{}+{}+{}'.format(WIDTH, HEIGHT, x, y))
# create label for information
info = tk.Label(root, text='no submissions')
info.pack()
# update label first time
update()
root.mainloop()

How to play a sound when a value increases by 1 between loops? What does the code look like that identifies the increment by 1?

I have designed and created a program using Python 3 that reads unread messages in my Gmail inbox under two labels.
By using tkinter I have two lovely boxes that display the total messages in each label. One for sales of one particular product and the other for another.
They use the update loop to recheck each label every few seconds.
Then after the business day, I use a cleanup script in Gmail that flushes the inboxes two labels.
The program is for use on my team's sales floor. They can see daily, the number of sales, and get a real-time readout to the success of certain marketing campaigns. It has done wonders for morale.
Now I would like to implement some sounds when sales go up. A cliche bell ring, a "chhaaaching" perhaps, you get the drift.
So, I am currently tackling with my limited knowledge and have searched all throughout StackOverflow and other sites for an answer. My guess is that I need something like the following...
"if an integer value changes on the next loop from it's previous value, by an increment of 1 play soundA, or, play soundB if that value decreases by 1."
I can't for the life of me figure out what would be the term for 'increases by 1', I am also clueless on how to attach a sound to any changes made to the integer on the proceeding loop. Help!!
If I wasn't clear enough, I am more than happy to explain and go into this further.
Thank you so much guys.
Here is my code as it stands so far...
#! /usr/bin/python3
import imaplib
import email
import tkinter as tk
WIDTH = 1000
HEIGHT = 100
def update():
mail=imaplib.IMAP4_SSL('imap.gmail.com',993)
mail.login('email#gmail.com','MyPassword')
mail.select("Submissions")
typ, messageIDs = mail.search(None, "UNSEEN")
FirstDataSetSUBS = str(messageIDs[0], encoding='utf8')
if FirstDataSetSUBS == '':
info1['text'] = 'no submissions'
else:
SecondDataSetSUBS = FirstDataSetSUBS.split(" ")
nosubs = len(SecondDataSetSUBS)
nosubs = int(nosubs)
info1['text'] = '{} submission[s]'.format(nosubs)
subs.after(1000, update)
def update_2():
mail=imaplib.IMAP4_SSL('imap.gmail.com',993)
mail.login('email#gmail.com','MyPassword')
mail.select("Memberships")
typ, messageIDs = mail.search(None, "UNSEEN")
FirstDataSetMSGS = str(messageIDs[0], encoding='utf8')
if FirstDataSetMSGS == '':
info2['text'] = 'no memberships'
else:
SecondDataSetMSGS = FirstDataSetMSGS.split(" ")
memberships = len(SecondDataSetMSGS)
memberships = int(memberships)
info2['text'] = '{} membership[s]'.format(memberships)
membs.after(1000, update_2)
membs = tk.Tk()
subs = tk.Tk()
membs.title('memberships counter')
membs.configure(background="black")
subs.title('submissions counter')
subs.configure(background="black")
x = (subs.winfo_screenwidth()//5) - (WIDTH//5)
y = (subs.winfo_screenheight()//5) - (HEIGHT//5)
subs.geometry('{}x{}+{}+{}'.format(WIDTH, HEIGHT, x, y))
info1 = tk.Label(subs, text='nothing to display', bg="black", fg="green", font="Lucida_Console 40")
info1.pack()
x = (membs.winfo_screenwidth()//2) - (WIDTH//2)
y = (membs.winfo_screenheight()//2) - (HEIGHT//2)
membs.geometry('{}x{}+{}+{}'.format(WIDTH, HEIGHT, x, y))
info2 = tk.Label(membs, text='nothing to display', bg="black", fg="red", font="Lucida_Console 40")
info2.pack()
update()
update_2()
membs.mainloop
subs.mainloop()
for playing audio you can use pyaudio module
in ubuntu, install by pip3 install pyaudio include sudo if required
import pygame
pygame.init()
increased=0
inc_music=pygame.mixer.music
dec_music=pygame.mixer.music
inc_music.load("/home/pratik/Documents/pos.wav")
dec_music.load("/home/pratik/Documents/neg.wav")
def get_inc():
global increased
a=increased
return a
def pl():
global inc_music
global dec_music
while True:
increased=get_inc()
if increased == 1:
inc_music.play()
increased=increased-1
elif increased == -1:
dec_music.play()
increased=increased+1
here increased is a global variable. Make sure whenever your sales is increased it is set to +1 and whenever it is decreased it is set to -1 and call pl function in a separate thread by using threading library in the background. Since, it is a forever loop it will continuosly run in background and ring the bells.
from threading import Thread
th=Thread(target=pl)
th.setDaemon(True)
th.start()
While writing the above I assumed at a moment there is either an increase or a decrease in sales, both dont occour simultaneously. If they do, use another global variable decreased which is also initialised with 0 and decreased by -1 each time decrease in sales. And return both of them from get_inc() .
Below code produces a label that gets updated randomly, and plays the sound files located in "C:\Windows\Media\" based on the change:
import tkinter as tk
import random
from playsound import playsound
def sound(is_positive=True):
if is_positive:
playsound(r"C:\Windows\Media\chimes.wav", False)
else:
playsound(r"C:\Windows\Media\chord.wav", False)
def random_update():
_old = label['text']
_new = random.randint(1, 100)
if _new > _old: # use (_new - _old) == 1 for checking
sound() # increment of 1 exclusively
elif _new < _old: # use (_new - _old) == -1 for checking
sound(False) # decrement of 1 exclusively
label['text'] = _new
label.after(2000, random_update)
if __name__ == '__main__':
root = tk.Tk()
label = tk.Label(root, text=1)
label.after(2000, random_update)
label.pack()
root.mainloop()
playsound is not a built-in package so it needs to be installed separately. You can install using:
pip install playsound
or:
python -m pip install playsound
in the command prompt on windows.

Tkinter '.after()' help, GUI window doesn't open

I'm trying to make an program for twitch, where the twitch chat can vote by typing a command ("!voteA") for example, and the chatbot-code will count the votes, and show them in a GUI window, I've already gotten pretty far, but my GUI window is not opening and I think it has something to do with the self.after(10, self.get_votes) line, could anyone help me out?
Here's the code: (it has some more tabs like Read, Socket, Initialize, Settings, but I don't think they're relevant for now, if they are I can also post them)
import Tkinter as tk
from Read import getUser, getMessage
from Socket import openSocket, sendMessage
from Initialize import joinRoom
from collections import OrderedDict
class App(tk.Frame): #Toplevel is a frame with a specific use. Unless there is another window, you want a Frame.
def __init__(self):
options = [
"voteA",
"voteB",
"voteC"]
self.s = openSocket()
joinRoom(self.s)
self.readbuffer = ""
tk.Frame.__init__(self)
self['background']='black'
self.pack()
self.master.geometry("+200+200")
# self.master.attributes("-alpha", 0.7)
self.labels = OrderedDict()
for option in options:
label = tk.Label(
self,
bg="black",
fg="white",
font="HouseSlant-Regular 30",
anchor="w")
label.pack()
self.labels[option] = [0, label]
self.update_text()
self.after(10, self.get_votes)
def update_text(self):
for name, data in self.labels.items():
count, label = data
label['text'] = "{} has {} votes".format(name, count)
def get_votes(self):
self.after(10, self.get_votes)
self.readbuffer = self.readbuffer + self.s.recv(1024)
temp = self.readbuffer.split('\n')
self.readbuffer = temp.pop() #save the last (possibly incomplete) line for later
if self.readbuffer == "":
pass #no further messages in the queue
#you should add a sleep time here for the sake of your hot NIC
for line in temp:
print(line)
if "PING" in line:
s.send("PONG :tmi.twitch.tv\r\n".encode())
break
user = getUser(line)
message = getMessage(line)
print "{} typed: {}".format(user, message)
if "!commands" in message:
sendMessage(self.s, " ".join(["!"+option for option in self.labels]))
break
for option in self.labels:
if "!"+option in message:
self.labels[option][0] += 1
print self.labels[option][0]
self.update_text()
app=App()
app.mainloop()
What happens when I run the code, is that the chatbot works; if I type "!commands" or "!voteA" it will respond to that nicely, however, the GUI window isn't opening. I can see in my dock (I'm a mac user) that it's trying to open a window, but it freezes and then I have to force quit it. It's not giving an error in the compiler though. Does anyone have any idea what I've messed up?

Tkinter window not playing well with threads

I've got a program that will eventually receive data from an external source over serial, but I'm trying to develop the display-side first.
I've got this "main" module that has the simulated data send and receive. It updates a global that is used by a Matplotlib stripchart. All of this works.
#-------------------------------------------------------------------------------
# Name: BBQData
# Purpose: Gets the data from the Arduino, and runs the threads.
#-------------------------------------------------------------------------------
import time
import math
import random
from threading import Thread
import my_globals as bbq
import sys
import BBQStripChart as sc
import serial
import BBQControl as control
ser = serial.serial_for_url('loop://', timeout=10)
def simData():
newTime = time.time()
if not hasattr(simData, "lastUpdate"):
simData.lastUpdate = newTime # it doesn't exist yet, so initialize it
simData.firstTime = newTime # it doesn't exist yet, so initialize it
if newTime > simData.lastUpdate:
simData.lastUpdate = newTime
return (140 + 0.05*(simData.lastUpdate - simData.firstTime), \
145 + 0.022*(simData.lastUpdate - simData.firstTime), \
210 + random.randrange(-10, 10))
else:
return None
def serialDataPump():
testCtr = 0;
while not bbq.closing and testCtr<100:
newData = simData()
if newData != None:
reportStr = "D " + "".join(['{:3.0f} ' for x in newData]) + '\n'
reportStr = reportStr.format(*newData)
ser.write(bytes(reportStr, 'ascii'))
testCtr+=1
time.sleep(1)
bbq.closing = True
def serialDataRcv():
while not bbq.closing:
line = ser.readline()
rcvdTime = time.time()
temps = str(line, 'ascii').split(" ")
temps = temps[1:-1]
for j, x in enumerate(temps):
bbq.temps[j].append(float(x))
bbq.plotTimes.append(rcvdTime)
def main():
sendThread = Thread(target = serialDataPump)
receiveThread = Thread(target = serialDataRcv)
sendThread.start()
receiveThread.start()
# sc.runUI()
control.runControl() #blocks until user closes window
bbq.closing = True
time.sleep(2)
exit()
if __name__ == '__main__':
main()
## testSerMain()
However, I'd like to add a SEPARATE tkinter window that just has the most recent data on it, a close button, etc. I can get that window to come up, and show data initially, but none of the other threads run. (and nothing works when I try to run the window and the plot at the same time.)
#-------------------------------------------------------------------------------
# Name: BBQ Display/Control
# Purpose: displays current temp data, and control options
#-------------------------------------------------------------------------------
import tkinter as tk
import tkinter.font
import my_globals as bbq
import threading
fontSize = 78
class BBQControl(tk.Tk):
def __init__(self,parent):
tk.Tk.__init__(self,parent)
self.parent = parent
self.labelFont = tkinter.font.Font(family='Helvetica', size=int(fontSize*0.8))
self.dataFont = tkinter.font.Font(family='Helvetica', size=fontSize, weight = 'bold')
self.makeWindow()
def makeWindow(self):
self.grid()
btnClose = tk.Button(self,text=u"Close")
btnClose.grid(column=1,row=5)
lblFood = tk.Label(self,anchor=tk.CENTER, text="Food Temps", \
font = self.labelFont)
lblFood.grid(column=0,row=0)
lblPit = tk.Label(self,anchor=tk.CENTER, text="Pit Temps", \
font = self.labelFont)
lblPit.grid(column=1,row=0)
self.food1Temp = tk.StringVar()
lblFoodTemp1 = tk.Label(self,anchor=tk.E, \
textvariable=self.food1Temp, font = self.dataFont)
lblFoodTemp1.grid(column=0,row=1)
#spawn thread to update temps
updateThread = threading.Thread(target = self.updateLoop)
updateThread.start()
def updateLoop(self):
self.food1Temp.set(str(bbq.temps[1][-1]))
def runControl():
app = BBQControl(None)
app.title('BBQ Display')
app.after(0, app.updateLoop)
app.mainloop()
bbq.closing = True
if __name__ == '__main__':
runControl()
Your title sums up the problem nicely: Tkinter doesn't play well with threads. That's not a question, that's the answer.
You can only access tkinter widgets from the same thread that created the widgets. If you want to use threads, you'll need your non-gui threads to put data on a queue and have the gui thread poll the queue periodically.
One way of getting tkinter to play well with threads is to modify the library so all method calls run on a single thread. Two other questions deal with this same problem: Updating a TKinter GUI from a multiprocessing calculation and Python GUI is not responding while thread is executing. In turn, the given answers point to several modules that help to solve the problem you are facing. Whenever I work with tkinter, I always use the safetkinter module in case threads appear to be helpful in the program.

Categories