I wrote a simple Tkinter based Python application that reads text from a serial connection and adds it to the window, specifically a text widged.
After a lot of tweaks and some very strange exceptions, this works. Then I added autoscrolling by doing this:
self.text.insert(END, str(parsed_line))
self.text.yview(END)
These lines run in a thread. The thread blocks on reading fromt the serial connection, splits lines and then adds all lines to the widget.
This works, too. Then I wanted to allow the user to scroll which should disable auto-scroll until the user scrolls back to the bottom.
I found this
Stop Text widget from scrolling when content is changed
which seems to be related. Especially, I tried the code from DuckAssasin's comment:
if self.myWidgetScrollbar.get() == 1.0:
self.myWidget.yview(END)
I also tried .get()[1] which is actually the element I want (bottom position). However, this crashes with the following exception:
Traceback (most recent call last):
File "transformer-gui.py", line 119, in run
pos = self.scrollbar.get()[1]
File "C:\Python26\lib\lib-tk\Tkinter.py", line 2809, in get
return self._getdoubles(self.tk.call(self._w, 'get'))
File "C:\Python26\lib\lib-tk\Tkinter.py", line 1028, in _getdoubles
return tuple(map(getdouble, self.tk.splitlist(string)))
ValueError: invalid literal for float(): None
It seems as if tkinter somewhere returns None which then is being parsed as a float. I read somewhere, that e.g. the index method of the text widged sometimes returnes None if the requested location is not visible.
Hopefully, anybody can help me out with this problem!
[EDIT]
Ok, I have assembled a demo script that can reproduce this issue on my Win XP machine:
import re,sys,time
from Tkinter import *
import Tkinter
import threading
import traceback
class ReaderThread(threading.Thread):
def __init__(self, text, scrollbar):
print "Thread init"
threading.Thread.__init__(self)
self.text = text
self.scrollbar = scrollbar
self.running = True
def stop(self):
print "Stopping thread"
running = False
def run(self):
print "Thread started"
time.sleep(5)
i = 1
try:
while(self.running):
# emulating delay when reading from serial interface
time.sleep(0.05)
line = "the quick brown fox jumps over the lazy dog\n"
curIndex = "1.0"
lowerEdge = 1.0
pos = 1.0
# get cur position
pos = self.scrollbar.get()[1]
# Disable scrollbar
self.text.configure(yscrollcommand=None, state=NORMAL)
# Add to text window
self.text.insert(END, str(line))
startIndex = repr(i) + ".0"
curIndex = repr(i) + ".end"
# Perform colorization
if i % 6 == 0:
self.text.tag_add("warn", startIndex, curIndex)
elif i % 6 == 1:
self.text.tag_add("debug", startIndex, curIndex)
elif i % 6 == 2:
self.text.tag_add("info", startIndex, curIndex)
elif i % 6 == 3:
self.text.tag_add("error", startIndex, curIndex)
elif i % 6 == 4:
self.text.tag_add("fatal", startIndex, curIndex)
i = i + 1
# Enable scrollbar
self.text.configure(yscrollcommand=self.scrollbar.set, state=DISABLED)
# Auto scroll down to the end if scroll bar was at the bottom before
# Otherwise allow customer scrolling
if pos == 1.0:
self.text.yview(END)
#if(lowerEdge == 1.0):
# print "is lower edge!"
#self.text.see(curIndex)
#else:
# print "Customer scrolling", lowerEdge
# Get current scrollbar position before inserting
#(upperEdge, lowerEdge) = self.scrollbar.get()
#print upperEdge, lowerEdge
#self.text.update_idletasks()
except Exception as e:
traceback.print_exc(file=sys.stdout)
print "Exception in receiver thread, stopping..."
pass
print "Thread stopped"
class Transformer:
def __init__(self):
pass
def start(self):
"""starts to read linewise from self.in_stream and parses the read lines"""
count = 1
root = Tk()
root.title("Tkinter Auto-Scrolling Test")
topPane = PanedWindow(root, orient=HORIZONTAL)
topPane.pack(side=TOP, fill=X)
lowerPane = PanedWindow(root, orient=VERTICAL)
scrollbar = Scrollbar(root)
scrollbar.pack(side=RIGHT, fill=Y)
text = Text(wrap=WORD, yscrollcommand=scrollbar.set)
scrollbar.config(command=text.yview)
# Color definition for log levels
text.tag_config("debug",foreground="gray50")
text.tag_config("info",foreground="green")
text.tag_config("warn",foreground="orange")
text.tag_config("error",foreground="red")
text.tag_config("fatal",foreground="#8B008B")
# set default color
text.config(background="black", foreground="gray");
text.pack(expand=YES, fill=BOTH)
lowerPane.add(text)
lowerPane.pack(expand=YES, fill=BOTH)
t = ReaderThread(text, scrollbar)
print "Starting thread"
t.start()
try:
root.mainloop()
except Exception as e:
print "Exception in window manager: ", e
t.stop()
t.join()
if __name__ == "__main__":
try:
trans = Transformer()
trans.start()
except Exception as e:
print "Error: ", e
sys.exit(1)
I let this scipt run and start to scroll up and down and after some time I get a lot of always different exceptions such as:
.\source\testtools\device-log-transformer>python tkinter-autoscroll.py
Thread init
Starting thread
Thread started
Traceback (most recent call last):
File "tkinter-autoscroll.py", line 59, in run
self.text.configure(yscrollcommand=self.scrollbar.set, state=DISABLED)
File "C:\Python26\lib\lib-tk\Tkinter.py", line 1202, in configure
Stopping thread
return self._configure('configure', cnf, kw)
File "C:\Python26\lib\lib-tk\Tkinter.py", line 1193, in _configure
self.tk.call(_flatten((self._w, cmd)) + self._options(cnf))
TclError: invalid command name ".14762592"
Exception in receiver thread, stopping...
Thread stopped
.\source\testtools\device-log-transformer>python tkinter-autoscroll.py
Thread init
Starting thread
Thread started
Stopping thread
Traceback (most recent call last):
File "tkinter-autoscroll.py", line 35, in run
pos = self.scrollbar.get()[1]
File "C:\Python26\lib\lib-tk\Tkinter.py", line 2809, in get
return self._getdoubles(self.tk.call(self._w, 'get'))
TclError: invalid command name ".14762512"
Exception in receiver thread, stopping...
Thread stopped
.\source\testtools\device-log-transformer>python tkinter-autoscroll.py
Thread init
Starting thread
Thread started
Traceback (most recent call last):
File "tkinter-autoscroll.py", line 65, in run
self.text.yview(END)
File "C:\Python26\lib\lib-tk\Tkinter.py", line 3156, in yview
self.tk.call((self._w, 'yview') + what)
Stopping threadTclError: invalid command name ".14762592"
Exception in receiver thread, stopping...
Thread stopped
.\source\testtools\device-log-transformer>python tkinter-autoscroll.py
Thread init
Starting thread
Thread started
Traceback (most recent call last):
File "tkinter-autoscroll.py", line 35, in run
pos = self.scrollbar.get()[1]
File "C:\Python26\lib\lib-tk\Tkinter.py", line 2809, in get
return self._getdoubles(self.tk.call(self._w, 'get'))
File "C:\Python26\lib\lib-tk\Tkinter.py", line 1028, in _getdoubles
return tuple(map(getdouble, self.tk.splitlist(string)))
ValueError: invalid literal for float(): None
Exception in receiver thread, stopping...
Thread stopped
Stopping thread
.\source\testtools\device-log-transformer>python tkinter-autoscroll.py
Thread init
Starting thread
Thread started
Traceback (most recent call last):
File "tkinter-autoscroll.py", line 53, in run
self.text.tag_add("error", startIndex, curIndex)
File "C:\Python26\lib\lib-tk\Tkinter.py", line 3057, in tag_add
(self._w, 'tag', 'add', tagName, index1) + args)
TclError: bad option "261.0": must be bbox, cget, compare, configure, count, debug, delete, dlineinfo, dump, edit, get, image, index, insert, mark, pe
er, replace, scan, search, see, tag, window, xview, or yview
Exception in receiver thread, stopping...
Thread stopped
I hope this helps you to help me :)
Thanks,
/J
It's hard to tell what's really going on but have you considered using a Queue?
from Tkinter import *
import time, Queue, thread
def simulate_input(queue):
for i in range(100):
info = time.time()
queue.put(info)
time.sleep(0.5)
class Demo:
def __init__(self, root, dataQueue):
self.root = root
self.dataQueue = dataQueue
self.text = Text(self.root, height=10)
self.scroller = Scrollbar(self.root, command=self.text.yview)
self.text.config(yscrollcommand=self.scroller.set)
self.text.tag_config('newline', background='green')
self.scroller.pack(side='right', fill='y')
self.text.pack(fill='both', expand=1)
self.root.after_idle(self.poll)
def poll(self):
try:
data = self.dataQueue.get_nowait()
except Queue.Empty:
pass
else:
self.text.tag_remove('newline', '1.0', 'end')
position = self.scroller.get()
self.text.insert('end', '%s\n' %(data), 'newline')
if (position[1] == 1.0):
self.text.see('end')
self.root.after(1000, self.poll)
q = Queue.Queue()
root = Tk()
app = Demo(root, q)
worker = thread.start_new_thread(simulate_input, (q,))
root.mainloop()
Regarding your demo script.
You're doing GUI stuff from the non-GUI thread. That tends to cause problems.
see: http://www.effbot.org/zone/tkinter-threads.htm
OK,
based on the valuable suggestions by noob oddy I was able to rewrite the example script by using the Tkinter.generate_event() method to generate asynchronous event and a queue to pass the information.
Every time a line is read from the stream (which is simulated by a constant string and a delay), I append the line to a queue (because passing objects to the event method is not supported AFAIK) and then create a new event.
The event callback method retrieves the message from the queue and adds it to the Text widged. This works because this method is called from the Tkinter mainloop an thus it cannot interfere with the other jobs.
Here is the script:
import re,sys,time
from Tkinter import *
import Tkinter
import threading
import traceback
import Queue
class ReaderThread(threading.Thread):
def __init__(self, root, queue):
print "Thread init"
threading.Thread.__init__(self)
self.root = root
self.running = True
self.q = queue
def stop(self):
print "Stopping thread"
running = False
def run(self):
print "Thread started"
time.sleep(5)
try:
while(self.running):
# emulating delay when reading from serial interface
time.sleep(0.05)
curline = "the quick brown fox jumps over the lazy dog\n"
try:
self.q.put(curline)
self.root.event_generate('<<AppendLine>>', when='tail')
# If it failed, the window has been destoyed: over
except TclError as e:
print e
break
except Exception as e:
traceback.print_exc(file=sys.stdout)
print "Exception in receiver thread, stopping..."
pass
print "Thread stopped"
class Transformer:
def __init__(self):
self.q = Queue.Queue()
self.lineIndex = 1
pass
def appendLine(self, event):
line = self.q.get_nowait()
if line == None:
return
i = self.lineIndex
curIndex = "1.0"
lowerEdge = 1.0
pos = 1.0
# get cur position
pos = self.scrollbar.get()[1]
# Disable scrollbar
self.text.configure(yscrollcommand=None, state=NORMAL)
# Add to text window
self.text.insert(END, str(line))
startIndex = repr(i) + ".0"
curIndex = repr(i) + ".end"
# Perform colorization
if i % 6 == 0:
self.text.tag_add("warn", startIndex, curIndex)
elif i % 6 == 1:
self.text.tag_add("debug", startIndex, curIndex)
elif i % 6 == 2:
self.text.tag_add("info", startIndex, curIndex)
elif i % 6 == 3:
self.text.tag_add("error", startIndex, curIndex)
elif i % 6 == 4:
self.text.tag_add("fatal", startIndex, curIndex)
i = i + 1
# Enable scrollbar
self.text.configure(yscrollcommand=self.scrollbar.set, state=DISABLED)
# Auto scroll down to the end if scroll bar was at the bottom before
# Otherwise allow customer scrolling
if pos == 1.0:
self.text.yview(END)
self.lineIndex = i
def start(self):
"""starts to read linewise from self.in_stream and parses the read lines"""
count = 1
self.root = Tk()
self.root.title("Tkinter Auto-Scrolling Test")#
self.root.bind('<<AppendLine>>', self.appendLine)
self.topPane = PanedWindow(self.root, orient=HORIZONTAL)
self.topPane.pack(side=TOP, fill=X)
self.lowerPane = PanedWindow(self.root, orient=VERTICAL)
self.scrollbar = Scrollbar(self.root)
self.scrollbar.pack(side=RIGHT, fill=Y)
self.text = Text(wrap=WORD, yscrollcommand=self.scrollbar.set)
self.scrollbar.config(command=self.text.yview)
# Color definition for log levels
self.text.tag_config("debug",foreground="gray50")
self.text.tag_config("info",foreground="green")
self.text.tag_config("warn",foreground="orange")
self.text.tag_config("error",foreground="red")
self.text.tag_config("fatal",foreground="#8B008B")
# set default color
self.text.config(background="black", foreground="gray");
self.text.pack(expand=YES, fill=BOTH)
self.lowerPane.add(self.text)
self.lowerPane.pack(expand=YES, fill=BOTH)
t = ReaderThread(self.root, self.q)
print "Starting thread"
t.start()
try:
self.root.mainloop()
except Exception as e:
print "Exception in window manager: ", e
t.stop()
t.join()
if __name__ == "__main__":
try:
trans = Transformer()
trans.start()
except Exception as e:
print "Error: ", e
sys.exit(1)
Thanks again to everybody who contributed for your help!
Related
first time questioner here!
I´m currently working on a python project that´s supposed to read soil moisture through a sensor on a raspberry pi, which then sends the data to another laptop through a socket connection. That other laptop displays the data in a UI. That UI is also capable of controllig the script flow. My Server starts up just fine, but when the client connects to it(connection itself works), I get the following Error Stack:
Exception in thread Thread-2:
Traceback (most recent call last):
File "/usr/lib/python3.5/threading.py", line 914, in _bootstrap_inner
self.run()
File "/usr/lib/python3.5/threading.py", line 862, in run
self._target(self._args, **self._kwargs)
File "/home/pi/Py_Scripts/MoistureSensor/Server.py", line 77, in worker_recv
self.JSON_Control = pickle.loads(self.data_recv)
EOFError: Ran out of input
Exception in thread Thread-1:
Traceback (most recent call last):
File "/usr/lib/python3.5/threading.py", line 914, in _bootstrap_inner
self.run()
File "/usr/lib/python3.5/threading.py", line 862, in run
self._target(self._args, **self._kwargs)
File "/home/pi/Py_Scripts/MoistureSensor/Server.py", line 70, in worker_send
self.conn.send(self.data_send)
BrokenPipeError: [Errno 32] Broken pipe
My Scripts look as follows:
Client-side:
import sys
from socket import *
from threading import Thread
from time import sleep
import pickle
from PySide6 import QtWidgets, QtGui
class MyClient:
def __init__(self, server_port, buf_size, host):
self.server_port = server_port
self.buf_size = buf_size
self.host = host
self.data_send = None
self.data_recv = None
self.exit = False
self.JSON_Control = {
"measure": False,
"exit": False,
}
self.JSON_Moisture = {
"moisture_level": 0
}
self.socket_connection = socket(AF_INET, SOCK_STREAM)
self.socket_connection.connect((self.host, self.server_port))
print("Connected with Server: %s: " % self.host)
# thread for sending
self.thread_send = Thread(target=self.worker_send)
# thread for receiving
self.thread_recv = Thread(target=self.worker_recv)
# starting Threads
self.thread_send.start()
self.thread_recv.start()
def worker_send(self):
while not self.exit:
self.data_send = pickle.dumps(self.JSON_Control)
self.socket_connection.send(self.data_send)
sleep(0.5)
def worker_recv(self):
while not self.exit:
self.data_recv = self.socket_connection.recv(self.buf_size)
if self.data_recv is not None:
self.JSON_Moisture = pickle.loads(self.data_recv)
class UiDisplay(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.moisture_label = None
self.moisture_level = None
self.start_button = None
self.refresh_button = None
self.stop_button = None
self.reset_button = None
self.quit_button = None
self.create_widgets()
def create_widgets(self):
# create a label to display the current time
self.moisture_label = QtWidgets.QLabel(self)
self.moisture_label.setFont(QtGui.QFont("Helvetica", 16))
self.moisture_level = client.JSON_Moisture["moisture_level"]
self.moisture_label.setText('Bodenfeuchtigkeit: ' + str(self.moisture_level)[:3] + '%')
# button to start the weight measuring
self.start_button = QtWidgets.QPushButton("Start measuring", self)
self.start_button.clicked.connect(self.measure_moisture)
# button to refresh the Weight label
self.refresh_button = QtWidgets.QPushButton("Refresh", self)
self.refresh_button.clicked.connect(self.refresh)
# button to stop measuring
self.stop_button = QtWidgets.QPushButton("Stop measuring", self)
self.stop_button.clicked.connect(self.stop_measuring)
# button to quit the program
self.quit_button = QtWidgets.QPushButton("Quit", self)
self.quit_button.clicked.connect(self.quit)
# create a layout to hold the widgets
layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.moisture_label)
layout.addWidget(self.start_button)
layout.addWidget(self.refresh_button)
layout.addWidget(self.stop_button)
layout.addWidget(self.reset_button)
layout.addWidget(self.quit_button)
self.setLayout(layout)
def measure_moisture(self):
client.JSON_Control["measure"] = True
def refresh(self):
# get current weight ( niederlag auf ein qm rechnen fehlt noch)
self.moisture_level = round(client.JSON_Moisture["moisture_level"] - 2.7) / (1 - 2.7)
print(self.moisture_level)
# umrechnen von analogem Wert zu Prozentanteil
print(self.moisture_level)
# update the weight label with the current time
self.moisture_label.setText('Bodenfeuchtigkeit: ' + str(self.moisture_level)[:5] + '%')
def stop_measuring(self):
if client.JSON_Control["measure"]:
client.JSON_Control["measure"] = False
else:
pass
def quit(self):
QtWidgets.QApplication.instance().quit()
client.JSON_Control["exit"] = True
sleep(2)
client.exit = True
client.thread_recv.join()
client.thread_send.join()
client.socket_connection.close()
print("Server connection is closed")
print('exiting...')
sleep(1)
sys.exit()
client = MyClient(1957, 1024, "192.168.86.121")
app = QtWidgets.QApplication()
window = UiDisplay()
window.show()
app.exec()
--------------------------------------------------------
The Server-side:
import sys
from socket import *
from threading import Thread
import pickle
from time import sleep
import Adafruit_ADS1x15
adc_channel_0 = 0
class SoilMoistureSensor:
def __init__(self, gain, sps, dry_voltage, saturated_voltage):
self.adc = Adafruit_ADS1x15.ADS1115()
self.raw_data = None
self.moisture_level = None
# self.voltage = None
self.gain = gain
self.sps = sps
self.dry_voltage = dry_voltage
self.saturated_voltage = saturated_voltage
class MyServer:
def __init__(self, echo_port, buf_size):
self.buf_size = buf_size
self.echo_port = echo_port
self.data_send = None
self.data_recv = None
self.exit = False
self.data_json = None
self.moisture_sensor = SoilMoistureSensor(2, 32, 1, 2.7) # gain, sps, saturated_voltage, dry_voltage
self.JSON_Control = {
"measure": False,
"exit": False
}
self.JSON_Moisture = {
"moisture_level": 0,
}
self.socket_connection = socket(AF_INET, SOCK_STREAM)
self.socket_connection.bind(("", self.echo_port))
self.socket_connection.listen(1)
print("Server gestartet")
print("Name des Hosts: ", gethostname())
print("IP des Hosts: ", gethostbyname(gethostname()))
self.conn, (self.remotehost, self.remoteport) = self.socket_connection.accept()
print("Verbunden mit %s %s " % (self.remotehost, self.remoteport))
# thread for sending
self.thread_send = Thread(target=self.worker_send)
# thread for receiving
self.thread_recv = Thread(target=self.worker_recv)
# thread for checking Json Control
self.thread_check_json_control = Thread(target=self.check_json_control)
# starting Threads
self.thread_send.start()
self.thread_recv.start()
self.thread_check_json_control.start()
def worker_send(self):
while not self.exit:
self.data_send = pickle.dumps(self.JSON_Moisture)
self.conn.send(self.data_send)
sleep(0.5)
def worker_recv(self):
while not self.exit:
self.data_recv = self.conn.recv(self.buf_size)
if self.data_recv is not None:
self.JSON_Control = pickle.loads(self.data_recv)
def measure_moisture(self, channel):
channel = adc_channel_0
self.moisture_sensor.raw_data = self.moisture_sensor.adc.read_adc(
channel, self.moisture_sensor.gain, self.moisture_sensor.sps)
self.JSON_Moisture["moisture_level"] = self.moisture_sensor.raw_data
print(self.moisture_sensor.raw_data)
def stop_connection(self):
self.thread_recv.join()
self.thread_send.join()
self.thread_check_json_control.join()
self.socket_connection.close()
print("Server connection is closed")
print('exiting...')
sys.exit()
def check_json_control(self):
while not self.exit:
if self.JSON_Control["measure"]:
self.measure_moisture(0)
if self.JSON_Control["exit"]:
self.stop_connection()
sleep(0.5)
server = MyServer(1957, 1024)
I´d be ever so grateful for help and I´m incredibly sorry if I´ve disregarded any question conventions. Cheers!
I have a pyglet program that runs a GUI in one thread and receives a message from another thread. It's supposed to update the GUI based on that message. The problem though is whenever I go to update a GUI element, I get an error message like this one:
Traceback (most recent call last):
File --- line 40, in run
pub.sendMessage("update", msg="Banana")
File --- line 77, in updateDisplay
self.label.text = msg
File "---\pyglet\text\__init__.py", line 289, in text
self.document.text = text
File "---\pyglet\text\document.py", line 294, in text
self.insert_text(0, text)
File "---\pyglet\text\document.py", line 425, in insert_text
self.dispatch_event('on_insert_text', start, text)
File "---\pyglet\event.py", line 408, in dispatch_event
if handler(*args):
File "---\pyglet\text\layout.py", line 1045, in on_insert_text
self._init_document()
File "---\pyglet\text\layout.py", line 1034, in _init_document
self._update()
File "---\pyglet\text\layout.py", line 957, in _update
lines = self._get_lines()
File "---\pyglet\text\layout.py", line 933, in _get_lines
glyphs = self._get_glyphs()
...
File "---\pyglet\font\base.py", line 246, in fit
region.blit_into(image, 0, 0, 0)
File "---\pyglet\image\__init__.py", line 1730, in blit_into
self.owner.blit_into(source, x + self.x, y + self.y, z + self.z)
File "---\pyglet\image\__init__.py", line 1622, in blit_into
glBindTexture(self.target, self.id)
File "---\pyglet\gl\lib.py", line 107, in errcheck
raise GLException(msg)
pyglet.gl.lib.GLException: b'invalid operation'
The operative error being pyglet.gl.lib.GLException: b'invalid operation'
I know self.label.text = Y works because it updates the text if I put it in the init() function. However, trying to do any kind of update from the update_display function (see code below) throws this error. I know the function gets called correctly because it successfully prints the message to console, but it breaks on the update element line. I've tried other elements than label and it's the same thing.
I have no idea why it's throwing this error or how to resolve it. Any ideas?
My code is:
import pyglet
from threading import Thread
from random import choice
from time import sleep
from pubsub import pub
RUN = True
class MessageThread(Thread):
def __init__(self):
"""Init Thread Class."""
Thread.__init__(self)
self.start() # start the thread
def run(self):
"""Run Thread."""
array = [0, 1, 2]
while True:
if RUN == False:
print("End runner")
break
value = choice(array)
sleep(1)
if value == 0:
print("event", value, ": Apple", flush=True)
pub.sendMessage("update", msg="Apple")
elif value == 1:
print("event", value, ": Banana", flush=True)
pub.sendMessage("update", msg="Banana")
else:
print("event", value, ": Carrot", flush=True)
pub.sendMessage("update", msg="Carrot")
class MyGUI(pyglet.window.Window):
# Note: With this structure, there is no "self.app", you just use
# self. This structure allows onDraw() to be called.
def __init__(self):
super(MyGUI, self).__init__(width=600,height=650,resizable=True)
self.label = pyglet.text.Label('Waiting...',
font_name='Times New Roman',
font_size=36,
x=self.width/2, y=self.height - 36,
anchor_x='center', anchor_y='center')
# create a pubsub receiver
# update is the topic subscribed to, updateDisplay the callable
pub.subscribe(self.updateDisplay, "update")
MessageThread()
def on_draw(self):
self.clear()
self.label.draw()
def updateDisplay(self, msg):
""" Receives data from thread and updates the display """
print ("Updating Display")
self.label.text = msg
def on_close(self):
""" Override the normal onClose behavior so that we can
kill the MessageThread, too
"""
print("Closing Application")
global RUN
RUN = False
self.close() # Need to include since defining on_close overrides it
if __name__ == "__main__":
window = MyGUI()
pyglet.app.run()
I haven't been doing almost any programming before this so I apologize in advance for the quality of my code.
The problem I'm having is that I have a GUI and I need to open a server to receive internet traffic, but if I try to open it in a new process I get an error about a name not being defined (even though it works just fine if I open the server as a regular function and not a new process). The funny thing is that the GUI still opens even though I get the error.
I'll paste the error messages after the code.
Thank you in advance.
from Tkinter import *
import socket, re
import netifaces as ni
from multiprocessing import Process, Manager
class Application(Frame):
def __init__(self, master):
""" Initialize the Frame"""
Frame.__init__(self,master)
self.grid()
self.create_widgets()
def create_widgets(self):
self.label1 = Label(text = "Target IPv6 address")
self.label1.grid(row=1, column=0)
self.entry1 = Entry(bd = 5)
self.entry1.grid(row=1, column = 1)
self.button1 = Button(text = "Start", command = self.clientstart)
self.button1.grid(row=1, column = 2)
pr1 = Process(target=self.serverstart)
self.button2 = Button(text = "Start", command = pr1.start())
self.button2.grid(row=2, column=2)
self.label2 = Label(text = "Choose interface to listen")
self.label2.grid(row=2, column=0)
self.interfaces = Menubutton(text="------", relief=RAISED)
self.interfaces.grid(row=2, column=1)
self.interfaces.menu = Menu(self.interfaces, tearoff=0)
self.interfaces["menu"] = self.interfaces.menu
self.menubox()
def menubox(self):
self.interfaces.menu.add_command(label="------", command = lambda interface="------": self.callback(interface))
for interface in ni.interfaces():
if interface.startswith('eth'):
self.interfaces.menu.add_command(label=interface, command = lambda interface=interface: self.callback(interface))
else:
pass
def callback(self, interface):
if interface.startswith('eth'):
self.interfaces["text"] = interface
else:
self.interfaces["text"] = "------"
self._netint = interface
def serverstart(self):
import tcpServer
tcpServer.start(self._netint)
def clientstart(self):
targetip = self.entry1.get()
import tcpClient
tcpClient.startclient(targetip)
root = Tk()
root.title("IPv6 traffic generator")
root.geometry("400x600")
app = Application(root)
root.mainloop()
Process Process-1:
Traceback (most recent call last):
File "/usr/lib/python2.7/multiprocessing/process.py", line 258, in _bootstrap
self.run()
File "/usr/lib/python2.7/multiprocessing/process.py", line 114, in run
self._target(*self._args, **self._kwargs)
File "gui.py", line 57, in serverstart
tcpServer.start(netint)
NameError: global name 'netint' is not defined
EDIT:
I tried the advice in the first reply, but it still gives an error
Process Process-1:
Traceback (most recent call last):
File "/usr/lib/python2.7/multiprocessing/process.py", line 258, in _bootstrap
self.run()
File "/usr/lib/python2.7/multiprocessing/process.py", line 114, in run
self._target(*self._args, **self._kwargs)
File "gui.py", line 55, in serverstart
tcpServer.start(self._netint)
AttributeError: Application instance has no attribute '_netint'
Here is the code for tcpServer.py
import socket
import netifaces as ni
def start(self, _netint):
host = ni.ifaddresses(self._netint)[ni.AF_INET6][0]['addr']
port = 5000
s = socket.socket(socket.AF_INET6)
s.bind((host, port))
s.listen(1)
c, addr = s.accept()
print "Connection from: " + str(addr)
while True:
data = c.recv(1024)
if not data:
break
print "from connected user: " + str(data)
data = str(data).upper()
print "sending: " + str(data)
c.send(data)
c.close()
if __name__ == '__main__':
start()
And here for tcpClient.py
import socket
def startclient(targetip):
host = targetip
port = 5000
s = socket.socket(socket.AF_INET6)
s.connect((host,port))
message = raw_input('Send message: ')
while message != 'q':
s.send(message)
data = s.recv(1024)
print 'Recieved from server: ' + str(data)
message = raw_input('Send message: ')
s.close()
if __name__ == '__main__':
startclient()
Please note that my server and client programs are just for testing for now.
I can't actually run your code to check, but it looks like the error can be fixed like this:
def callback(self, interface):
if interface.startswith('eth'):
self.interfaces["text"] = interface
else:
self.interfaces["text"] = "------"
self._netint = interface
def serverstart(self):
import tcpServer
tcpServer.start(self._netint)
Generally, it's best to avoid using global as much as possible. One of the benefits of using classes is that it provides a shared namespace that all the methods of class instances can access via self.
Using Pyro4 I haven't managed to successfully execute a callback from server to client.
The server script looks as follows:
class RobotController(object):
def __init__(self):
self.robotStates = []
def moveDemo(self, motionTime):
stopTime = datetime.datetime.now() + datetime.timedelta(0,motionTime)
while datetime.datetime.now() < stopTime:
print "Robot is moving..."
time.sleep(1)
print "Robot stopped"
return 0
def doCallback(self, callback):
print("server: doing callback 1 to client")
try:
callback.call1()
except:
print("got an exception from the callback.")
print("".join(Pyro4.util.getPyroTraceback()))
print("server: doing callback 2 to client")
try:
callback.call2()
except:
print("got an exception from the callback.")
print("".join(Pyro4.util.getPyroTraceback()))
print("server: callbacks done")
if __name__ == '__main__':
robotController = RobotController()
if os.name == 'posix':
daemon = Pyro4.Daemon(host="192.168.1.104", port=8000);
else:
daemon = Pyro4.Daemon(host="localhost", port=8000);
Pyro4.Daemon.serveSimple(
{ robotController: "robotController"},
ns=False,
daemon=daemon,
verbose = True
)
and the client looks as follows:
class CallbackHandler(object):
def crash(self):
a=1
b=0
return a//b
def call1(self):
print("callback 1 received from server!")
print("going to crash - you won't see the exception here, only on the server")
return self.crash()
#Pyro4.callback
def call2(self):
print("callback 2 received from server!")
print("going to crash - but you will see the exception here too")
return self.crash()
daemon = Pyro4.core.Daemon()
callback = CallbackHandler()
daemon.register(callback)
#robotController = Pyro4.Proxy("PYRO:robotController#192.168.1.104:8000")
robotController = Pyro4.Proxy("PYRO:robotController#localhost:8000")
robotController._pyroOneway.add("doCallback")
robotController.doCallback(callback)
When executing the command robotController.doCallback(callback), the method doCallback on server is executed, but the server cannot access the client. It returns the following:
Traceback (most recent call last):
File "C:\LASTNO\Python Projects\PiBot\RobotServer\PyroServer\pyroServer.py", line 63, in doCallback
callback.call2()
File "C:\Python27\lib\site-packages\Pyro4\core.py", line 160, in __call__
return self.__send(self.__name, args, kwargs)
File "C:\Python27\lib\site-packages\Pyro4\core.py", line 286, in _pyroInvoke
self.__pyroCreateConnection()
File "C:\Python27\lib\site-packages\Pyro4\core.py", line 371, in __pyroCreateConnection
raise ce
CommunicationError: cannot connect: [Errno 10061] No connection could be made because the target machine actively refused it
Does anyone know what could be the cause of the error and how to fix it? Thank you!
I solved the problem by modifying the client code as follows:
class CallbackHandler(object):
def crash(self):
a=1
b=0
return a//b
def call1(self):
print("callback 1 received from server!")
print("going to crash - you won't see the exception here, only on the server")
return self.crash()
#Pyro4.callback
def call2(self):
print("callback 2 received from server!")
print("going to crash - but you will see the exception here too")
return self.crash()
daemon = Pyro4.core.Daemon()
callback = CallbackHandler()
daemon.register(callback)
with Pyro4.core.Proxy("PYRO:robotController#localhost:8000") as server:
server._pyroOneway.add("doCallback")
server.doCallback(callback)
motion = server.moveDemo(15)
print("waiting for callbacks to arrive...")
print("(ctrl-c/break the program once it's done)\n")
daemon.requestLoop()
I have this little program I wrote, In it there is a class of methods, and a class that build the window (only one).
from Tkinter import *
from tkMessageBox import *
import socket
import platform ,sys
import subprocess
from multiprocessing.pool import ThreadPool
import Queue
import threading
class Methods(object):
def __init__(self):
#TODO : implement
pass
def getHostName(self):
try:
return socket.gethostname()
except:
return "ERROR :Could'nt get Hostname"
def getOperatingSystem(self):
try:
return platform.system() + " " + platform.release() + " " + platform.version() + " " + sys.getwindowsversion()[4]
except:
return "ERROR :Could'nt get Operating System"
def getHotFixes(self,queue):
try:
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
myProcess = subprocess.Popen(
"wmic qfe get HotFixID, InstalledOn",
stdout = subprocess.PIPE,
stderr = subprocess.PIPE,
startupinfo = startupinfo)
out, error = myProcess.communicate()
full_list = out.splitlines()
result = ""
for item in full_list:
if item != "" and item != " ":
result += "%s \n" % item
out_number = len(result.splitlines()) - 1
a = "There Are %s Microsoft HotFixes Updates \n\n%s" % (out_number , result)
queue.put(a)
except:
return "ERROR :Could'nt get HotFixes"
#VISUAL
#This class will have an instance of Methods and call every action by itself.
class MainWindow(object):
def __init__(self):
self.root = Tk()
self.root.title('SAAP')
self.root.geometry('610x440+100+100')
#self.root.resizable(0,0)
self.methods = Methods()
def openHostName():
disableAllButtons(self)
result = self.methods.getHostName()
print result
self.textLabelString.set("Host Name")
self.textBox.config(state=NORMAL)
self.textBox.delete("1.0",END)
self.textBox.insert(INSERT,result)
self.textBox.config(state=DISABLED)
enableAllButtons(self)
def openOperatingSystem():
disableAllButtons(self)
result = self.methods.getOperatingSystem()
print result
self.textLabelString.set("Operating System")
self.textBox.config(state=NORMAL)
self.textBox.delete("1.0",END)
self.textBox.insert(INSERT,result)
self.textBox.config(state=DISABLED)
enableAllButtons(self)
def openHotFixes():
queue = Queue.Queue()
thread_ = threading.Thread(
target = self.methods.getHotFixes,
name='Thread1',
args=[queue],
)
thread_.start()
thread_.join()
result = queue.get()
disableAllButtons(self)
self.textLabelString.set("Microsoft Hotfixes")
self.textBox.config(state=NORMAL)
self.textBox.delete("1.0",END)
self.textBox.insert(INSERT,result)
self.textBox.config(state=DISABLED)
enableAllButtons(self)
#items decleration
self.actionLabel = Label(self.root, text = 'Actions',bg='blue',fg='white')
self.button1 = Button(self.root, text = 'Host Name' , command=openHostName)
self.button2 = Button(self.root, text = 'Operating System' , command = openOperatingSystem)
self.button3 = Button(self.root, text = 'Microsoft HotFixes' , command = openHotFixes)
self.button4 = Button(self.root, text = 'N4')
self.button5 = Button(self.root, text = 'Fi5o')
self.button6 = Button(self.root, text = 'C6y')
self.button7 = Button(self.root, text = '7')
self.button8 = Button(self.root, text = '8y')
self.button9 = Button(self.root, text = 'R9s')
self.button10 = Button(self.root, text = '10t')
self.button11 = Button(self.root, text = 'I11s')
self.textLabelString = StringVar()
self.textLabel = Label(self.root,bg='black',fg='white',width=60,textvariable=self.textLabelString)
self.textLabelString.set("Output")
self.textBox = Text(self.root,width=52)
self.textBox.insert(INSERT,"Here's the output")
self.textBox.config(state=DISABLED)
self.scrollBar = Scrollbar(self.root)
self.scrollBar.config(command=self.textBox.yview)
self.textBox.config(yscrollcommand=self.scrollBar.set)
#items placing
self.actionLabel.grid(row=0,column=0,sticky=W+E+N+S,pady=5)
self.button1.grid(row=1,column=0,padx=5,pady=5,sticky=W+E)
self.button2.grid(row=2,column=0,padx=5,pady=5,sticky=W+E)
self.button3.grid(row=3,column=0,padx=5,pady=5,sticky=W+E)
self.button4.grid(row=4,column=0,padx=5,pady=5,sticky=W+E)
self.button5.grid(row=5,column=0,padx=5,pady=5,sticky=W+E)
self.button6.grid(row=6,column=0,padx=5,pady=5,sticky=W+E)
self.button7.grid(row=7,column=0,padx=5,pady=5,sticky=W+E)
self.button8.grid(row=8,column=0,padx=5,pady=5,sticky=W+E)
self.button9.grid(row=9,column=0,padx=5,pady=5,sticky=W+E)
self.button10.grid(row=10,column=0,padx=5,pady=5,sticky=W+E)
self.button11.grid(row=11,column=0,padx=5,pady=5,sticky=W+E)
self.textLabel.grid(row=0,column=1,padx=10,pady=5)
self.textBox.grid(row=1,column=1,rowspan=11,pady=5)
self.scrollBar.grid(row=1,column=2,rowspan=11,sticky=N+S)
def disableAllButtons(self):
self.button1['state'] = DISABLED
self.button2['state'] = DISABLED
self.button3['state'] = DISABLED
self.button4['state'] = DISABLED
self.button5['state'] = DISABLED
self.button6['state'] = DISABLED
self.button7['state'] = DISABLED
self.button8['state'] = DISABLED
self.button9['state'] = DISABLED
self.button10['state'] = DISABLED
self.button11['state'] = DISABLED
def enableAllButtons(self):
self.button1['state'] = NORMAL
self.button2['state'] = NORMAL
self.button3['state'] = NORMAL
self.button4['state'] = NORMAL
self.button5['state'] = NORMAL
self.button6['state'] = NORMAL
self.button7['state'] = NORMAL
self.button8['state'] = NORMAL
self.button9['state'] = NORMAL
self.button10['state'] = NORMAL
self.button11['state'] = NORMAL
def main():
mainw = MainWindow()
mainw.root.mainloop()
if __name__ == "__main__":
main()
Now , My problem is when I press a button it needs to do something and then the output should appear on screen.
BUT, and here comes the but --
when the action takes a bit, it freezes the program until the action is done.
I want to make the program treat maybe the action as a different thread so it won't freeze.
I tried some stuff but it did not worked for me unfortunately ...
Any Help ?
Appreciated!
It is ok to execute your actions in separate threads, however you need to implement a
mechanism for signaling to your main thread (where Tk's loop is running) when actions
are finished, and to get the result(s).
One approach is to have a proper Action class, creating thread objects ; you pass
the method to execute and its arguments, then you start the thread - beforehand,
you register a callback that will be called in your Tk loop when action is finished.
In order to pass results from the thread to the callback, a Queue can be used:
import functools
class Action(threading.Thread):
def __init__(self, method, *args):
threading.Thread.__init__(self)
self.daemon = True
self.method=method
self.args=args
self.queue=Queue.Queue()
def run(self):
self.queue.put(self.method(*self.args))
def register_callback(self, tkroot, callback):
# to be called by Tk's main thread,
# will execute the callback in the Tk main loop
try:
result = self.queue.get_nowait()
except:
# set a timer, to check again for results within 100 milliseconds
tkroot.after(100, functools.partial(self.register_callback,
tkroot, callback))
else:
return callback(result)
EDIT: modification of the original example to show how to apply this to the getHotFixes
method
As an example, here is how to change getHotFixes accordingly:
class Methods(object):
...
def getHotFixes(self):
try:
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
myProcess = subprocess.Popen("wmic qfe get HotFixID, InstalledOn",
stdout = subprocess.PIPE,
stderr = subprocess.PIPE,
startupinfo = startupinfo)
out, error = myProcess.communicate()
full_list = out.splitlines()
result = ""
for item in full_list:
if item != "" and item != " ":
result += "%s \n" % item
out_number = len(result.splitlines()) - 1
return "There Are %s Microsoft HotFixes Updates \n\n%s" % (out_number , result)
except:
return "ERROR :Could'nt get HotFixes"
Finally, in MainWindow you just need to call the getHotFixes method, register
a callback to do something useful with the result when it's finished using
register_callback and call start() to start the Action thread:
class MainWindow(object):
def __init__(self):
self.root = Tk()
...
def openHotFixes():
disableAllButtons(self)
action = Action(self.methods.getHotFixes)
action.register_callback(self.root, openHotFixesDone)
action.start()
def openHotFixesDone(result):
self.textLabelString.set("Microsoft Hotfixes")
self.textBox.config(state=NORMAL)
self.textBox.delete("1.0",END)
self.textBox.insert(INSERT,result)
self.textBox.config(state=DISABLED)
enableAllButtons(self)
Hope this helps.