I'm trying to make a Tkinter based GUI for an Arduino printing sensors value and responding to user input.
The code I'm trying to use to eliminate while loops is this, which doesn't print any sensor information, the only output is "Trying.../dev/ttyACM0" followed by the tkinter window opening.
import serial
import time
from Tkinter import *
connected = False
write_to_file_path = "output.txt"
output_file = open(write_to_file_path, "w+")
locations=['/dev/ttyACM0','/dev/ttyACM1','/dev/ttyACM2','/dev/ttyACM3']
for device in locations:
try:
print "Trying...",device
ser = serial.Serial(device, 9600)
break
except:
print "Failed to connect on",device
## loop until the arduino tells us it is ready
while not connected:
serin = ser.read()
connected = True
time.sleep(0.1)
ser.flushInput()
time.sleep(1)
def serialRead():
if ser.inWaiting():
line = ser.readline()
data = line.decode("utf-8").split('\t')
print(data)
output_file.write(line)
root.after(1000, serialRead)
root = Tk()
root.title("Temperature Control")
serialRead()
root.mainloop()
This, on the other hand, works perfectly with the exception of not having a tkinter window. But it removes old input from the buffer and reads in the new input.
import serial
import time
connected = False
write_to_file_path = "output.txt"
output_file = open(write_to_file_path, "w+")
serial_port = '/dev/ttyACM0'
baud_rate = 9600
ser = serial.Serial(serial_port, baud_rate, timeout=5)
time.sleep(0.1)
ser.flushInput()
time.sleep(1)
while True:
if ser.inWaiting():
line = ser.readline()
data = line.decode("utf-8").split('\t') #ser.readline returns a binary, convert to string
print data[0] + '\t' + data[1]
output_file.write(line)
This was inspired by a different stackoverflow post from a while ago: Run an infinite loop in the backgroung in Tkinter
I've seen some example using threading but I don't know much about python nor threading so I'd really like to get it to work with root.after() if that's possible. I've also tried the example using root.after, all of which are pretty similar to the one I linked, and I couldn't get any of them working. Am I doing anything obviously wrong or in a way that's much more difficult than it needs to be? I would appreciate it greatly if someone would point me in the right direction.
I made a UI in TK for reading data from a GPS receiver and I had difficulties getting root.mainloop() to run, so instead I put a callback inside the TK app that ends up calling root.update() rather than mainloop.
The code looks something like this:
class App:
def __init__(self, master):
self.sats = tk.StringVar()
self.satsnum = tk.Label(self.frame, textvariable=self.sats, bg="blue")
self.satsnum.pack()
def update_display(self, master):
while source:
self.sats.set(n_sats)
if n_sats < 10:
satsbgcolor = 'red'
else:
satsbgcolor = 'green'
self.satsnum.configure(bg = satsbgcolor)
master.update()
time.sleep(1)
with serial_link.get_base_args_driver(args) as driver:
with Handler(Framer(driver.read, driver.write, verbose=True)) as source:
root = tk.Tk()
app = App(root)
app.update_display(root)
Note, the time.sleep(1) is necessary on MacOS as tk.update() will leak memory if the update is called too fast.
Related
I want to trigger an event whenever there is data to be read from a serial port while running a GUI. The pySerial module apparently has experimental functionality for that, but it isn't particularly well documented (I couldn't find any useful examples in the API).
This question appears to deal with the same or at least very similar task, but doesn't provide instructions to replicate it or working code examples.
I came up with this code:
import tkinter as tk
import serial
import threading
# Create GUI window
window = tk.Tk()
# Initialize the port
myPort = serial.Serial('/dev/ttyUSB0')
# Function to call whenever there is data to be read
def readFunc(port):
port.readline()
print('Line read')
# Configure threading
t1 = threading.Thread(target = readFunc, args=[myPort])
t1.start()
# Main loop of the window
window.mainloop()
Running it does indeed trigger the event, but only once. Why is that? Is there a "recommended" way to do this as by using the functionality of pySerial itself?
Alternatively, I would also run the function to read and process data on an event like you can with GUI elements. If that is the better solution, how would that be done?
Related question (unanswered), probably makes this question a duplicate
Edit: Here is a minimal example derived from the answer below that changes the text of a label whenever data is read to the incoming data:
import tkinter as tk
from serial import Serial
from serial.threaded import ReaderThread, Protocol
app = tk.Tk()
label = tk.Label(text="A Label")
label.pack()
class SerialReaderProtocolRaw(Protocol):
port = None
def connection_made(self, transport):
"""Called when reader thread is started"""
print("Connected, ready to receive data...")
def data_received(self, data):
"""Called with snippets received from the serial port"""
updateLabelData(data)
def updateLabelData(data):
data = data.decode("utf-8")
label['text']=data
app.update_idletasks()
# Initiate serial port
serial_port = Serial("/dev/ttyACM0")
# Initiate ReaderThread
reader = ReaderThread(serial_port, SerialReaderProtocolRaw)
# Start reader
reader.start()
app.mainloop()
Your main concern is to be thread safe, when You are updating GUI from another running Thread.
To achieve this, we can use .after() method, which executes callback for any given tk widget.
Another part of Your request is to use Threaded serial reader.
This can be achieved by using ReaderThread accompanied with Protocol.
You can pick two protocols:
raw data reader protocol, which reads data as they come
line reader protocol, which enables us to read lines of data
Here is working code example, with two protocols mentioned above, so You can pick which one suits You. Just remember, that all data coming from serial port are just raw bytes.
import tkinter as tk
from serial import Serial
from serial.threaded import ReaderThread, Protocol, LineReader
class SerialReaderProtocolRaw(Protocol):
tk_listener = None
def connection_made(self, transport):
"""Called when reader thread is started"""
if self.tk_listener is None:
raise Exception("tk_listener must be set before connecting to the socket!")
print("Connected, ready to receive data...")
def data_received(self, data):
"""Called with snippets received from the serial port"""
self.tk_listener.after(0, self.tk_listener.on_data, data.decode())
class SerialReaderProtocolLine(LineReader):
tk_listener = None
TERMINATOR = b'\n\r'
def connection_made(self, transport):
"""Called when reader thread is started"""
if self.tk_listener is None:
raise Exception("tk_listener must be set before connecting to the socket!")
super().connection_made(transport)
print("Connected, ready to receive data...")
def handle_line(self, line):
"""New line waiting to be processed"""
# Execute our callback in tk
self.tk_listener.after(0, self.tk_listener.on_data, line)
class MainFrame(tk.Frame):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.listbox = tk.Listbox(self)
self.listbox.pack()
self.pack()
def on_data(self, data):
print("Called from tk Thread:", data)
self.listbox.insert(tk.END, data)
if __name__ == '__main__':
app = tk.Tk()
main_frame = MainFrame()
# Set listener to our reader
SerialReaderProtocolLine.tk_listener = main_frame
# Initiate serial port
serial_port = Serial("/dev/ttyUSB0")
# Initiate ReaderThread
reader = ReaderThread(serial_port, SerialReaderProtocolLine)
# Start reader
reader.start()
app.mainloop()
I am building a GUI in Tkinter and I want to receive incoming data from Serial (from a microcontroller).
Also, I want to print that out onto the text-editor widget in Tkinter (similar to the serial monitor console).
For that, I am right now trying to write a receiving code in Python without Tkinter but I am unable to receive any data in the console.
The code is as follows:
import serial
import time
import threading
global serial_open
serial_open = False
def serial_read():
global ser
global var
while True:
if serial_open == True:
var = ser.readline()
if var != "":
print(var)
else:
pass
ser = serial.Serial('COM3', baudrate = 19200)
time.sleep(3)
serial_open = True
print("COM3 Connected")
threading.Thread(target = serial_read).start()
time.sleep(10)
ser.close()
print("Disconnected")
I'm trying to read a very slow sensor (1-wire) while still operating other functions. I've replaced the read sensors with a sleep of 3 sec. I'd like to understand if I can have below time concurrently print accurate time every second while the sensor (wait 3sec.) occurs. This threading concept is new to me.
import time
import threading
from tkinter import Tk
def events_every_second(): #update clock every second
right_now = time.strftime("%I:%M:%S %p")#("%H:%M:%S")
print("time is now",right_now)
root.after(1000, events_every_second)
def one_wire():
time.sleep(3)
print("one_wire loop")
root.after(3010, one_wire)
root = Tk()
t_one_wire = one_wire()
thread_one_wire = threading.Thread(target = t_one_wire)
thread_one_wire.start()
t_ees = events_every_second
thread_ees = threading.Thread(target = t_ees)
thread_ees.start()
root.mainloop()
Just the function that does the lenghty sensor read needs to be
in a separate thread.
You can use a queue.Queue to obtain data from it -
in the example bellow I inserted the value fetching in the
every_one_sec function.
The every_one_sec function is handled by Tkinter scheduling events -
no need to creat a thread for it.
Other than that, the most incorrect part in your code was doing a
full call to one_wire before creating the thread in
t_one_wire = one_wire() - and the fact that it would run also require
tkinter to call it back. Tkinter wants to run all its events in the same
thread - so this would lead to problems.
I hope the comments bellow are enough for a better comprehension
import time
import threading
from queue import Queue, Empty
from tkinter import Tk
def some_ui_code():
global stop_reading
stop_reading = True
def events_every_second(): #update clock every second
right_now = time.strftime("%I:%M:%S %p")#("%H:%M:%S")
print("time is now",right_now)
try:
result = queue.get(block=False)
except Empty:
result = None
if result is not None:
# code to display the result read from the sensor in tkinter interface goes here
...
# tkinter, not a separate thread, handles this
root.after(1000, events_every_second)
def one_wire(queue):
# this is handled in a separate thread, and "does not know" about tkinter at all
while not stop_reading:
result = call_to_read_sensor() # takes 3 seconds
queue.put(result)
print("one_wire loop")
time.sleep(0.1) # actual call to time.sleep to space sensor readings, if needed.
root = Tk()
stop_reading = False
queue = Queue()
# start events_every_second - tkinter will handle the subsequent calls with the `root.after` scheduling
events_every_second()
thread_one_wire = threading.Thread(target=t_one_wire, args=(queue,))
thread_one_wire.start()
root.mainloop()
You should not use root.after(3010,one_wire) this will cause your Tk GUI to hang for 3 seconds and don't update.
You want to create a new Thread for this function so it does not stop your Tk app.
def one_wire():
time.sleep(3)
print("one_wire_loop")
create a new thread for this function.
import threading
one_wire_thread = threading.Thread(target = one_wire, args = ())
one_wire_thread.start()
Note that the code above will only run your function once. you could create a wrapper function with a while True: in there to keep it running, in the wrapper function you could also check for condition to stop and break the function and thus stop the one_wire function. you could also do this in the one_wire function:
def one_wire():
while True:
time.sleep(3)
print("one_wire_loop")
if {"condition to stop reading sensor"}:
break
I have this code which downloads the files specified in an online list and whilst doing that, display a loading screen with a label telling of which file is being downloaded and an indeterminate progress bar showing to the user something is happening. The download works very well, but what doesn't work is tkinter, which doesn't work until the process is finished, only showing the "Download Finished" label at the end. How can I get tkinter to display a window before a function is finished?
I have already tried
Spliting it up into multiple functions
Adding a sleep function to see if slowing it down helps
To display this what I am talking about, I have replaced my original code with some examples. Does anyone know how to make tkinter update more actively (before the function has finished)?
#Imports
import urllib.request as ur
import os
from tkinter import *
import tkinter.ttk as ttk
import time as T
#Globals
tk = None
where = None
progressbar = None
progresstitle = None
progressinfo = None
transfer = None
#Make sure that the download directory exists
def checkdir(filename):
directory = os.path.dirname(filename)
try:
os.stat(directory)
except:
os.mkdir(directory)
#First part (read q to see why I split up)
def part1():
#Get Globals
global tk
global where
global progressbar
global progresstitle
global progressinfo
global transfer
#Create Window
tk = Tk()
tk.title("Downloading...")
#Find out the location of the online files to download by reading the online txt file which contains their locations
where = str(ur.urlopen("http://example.com/whatfilestodownload.txt").read())
where = where[2:(len(where)-1)]
where = where.split(";")
#Create the progress bar
progressbar = ttk.Progressbar(tk, orient=HORIZONTAL, length=200, mode='indeterminate')
progressbar.grid(row = 2, column = 0)
#Create the labels
progresstitle = Label(tk, text = "Downloading Files!", font = ("Helvetica", 14))
progresstitle.grid(row = 0, column = 0)
progressinfo = Label(tk, text = "Starting Download...", font = ("Helvetica", 10))
progressinfo.grid(row = 1, column = 0)
#Engage Part Two
part2()
#Part Two
def part2():
#Get Globals
global tk
global where
global progressbar
global progresstitle
global progressinfo
global transfer
#Repeat as many times as files described in the only file describing .txt
for x in where
#The online file contains "onlinelocation:offlinelocation" This splits them up
x1 = x.split(":")[0]
x2 = x.split(":")[1]
#Read the online file and update labels
transfer = None
progressinfo.config(text = str("Downloading " + x2 + "..."))
transfer = str(ur.urlopen("http://example.com/" + x1).read())
progressinfo.config(text = str("Configuring downloaded file..."))
transfer = transfer [2:(len(transfer)-1)]
#Fix python turning "\n" to "\\n" by reversing
transfer = transfer.split("\\n")
transtemp = ""
for x in transfer:
transtemp = transtemp + "\n" + x
transfer = transtemp[1:len(transtemp)]
progressinfo.config(text = str("Installing " + x2 + "..."))
tw = None
checkdir(str(os.getcwd()+"/Downladed/"+x2))
tw = open(str(os.getcwd()+"/Downloaded/"+x2), "w")
tw.write(transfer)
tw.close()
#See if waiting helps
T.sleep(0.5)
part3()
def part3():
#Get Globals
global tk
global where
global progressbar
global progresstitle
global progressinfo
global transfer
#Final Screen
progressbar.grid_remove()
progressinfo.grid_remove()
progresstitle.config(text="You have downloaded\n the required files!")
progressbar.stop()
part1()
If updating the display at the end of each file being downloaded in your part2() function is enough, you can use the update_idletasks() method, putting it in place of T.sleep(), which will allow the GUI to refresh between going back to another iteration of your for loop.
Ref: http://effbot.org/tkinterbook/widget.htm#Tkinter.Widget.update_idletasks-method
This is why Tcl has asynchronous I/O functions. You need to keep processing windowing system events in a timely manner so you cannot wait for a complete file to download. Instead you need to do it in pieces. In Tcl we would use the fileevent command to set a procedure to be called each time some input became available from the socket. The rest of the time we can process other events. In Python the common way to do this is the Twisted package. This allows you to register event sources with twisted and make the entire application event oriented. You could also use threads and do the downloading on worker threads but that doesn't really help you with the progress notifications. There is some special handling to link up Tkinter and Twisted - see the documentation.
I'm trying to build a gui for communication with a serial device. For this I'm using Tkinter. My problem is, that every time I execute the script only the estCon-function is executed and the mainloop, and therefore the gui is never started. If I place the definition of the estCon function after the main loop it says that the estCon function was not found.
def estCon():
# establish connection
while True:
try:
ser = serial.Serial(port, baud, bytesize)
print('Connected.')
break
except serial.SerialException:
print('waiting for device ' + port + ' to be available.')
time.sleep(3)
starttime = time.time()
outfile = open(filename, 'a')
doprint = True
root = Tk()
estConButton = Button(root, text="Establish serial connection",
command=estCon())
estConButton.pack()
root.mainLoop()
You need to change this line:
estConButton = Button(root, text="Establish serial connection", command=estCon())
To:
estConButton = Button(root, text="Establish serial connection", command=estCon)
Notice the lack of parentheses (). Basically, you need to pass a reference to the function that will be called when you press the button and not an actual call.