I am writing a Python script that uses Exscript to log into network switches to gather information. I'm using Tkinter to present a graphical interface.
I'm trying to create a canvas window with status so that when the "go" button is clicked, the status windows is made visible and as the script logs into devices a label on the status window is updated (logging into device x of y).
I found the .update() and .update_idletasks(), which works when I enter the initial function (clicking the go button), but updating the label happens 2 functions deep in the Exscript start() function. In there anything I do to my canvas causes the script to hang and I have to force close it.
onhost = 0
hostcount = 0
def go_main():
canvas.itemconfig(statuswindow, state='normal')
canvas.itemconfig(statuslabel_window, state='normal')
canvas.update()
filename = InvEntry.get()
accounts = [Account(unameEntry.get(), pwordEntry.get())]
workbook = xlrd.open_workbook(filename)
sheet = workbook.sheet_by_name("Infrastructure")
global hostcount
hostcount = sheet.nrows
hosts = []
for x in range (1, hostcount):
hosts.append(conproto.get() + "://" + sheet.cell_value(x, 0))
start(accounts, hosts, login_func)
def login_func(job, host, conn):
global onhost
global hostcount
onhost = onhost + 1
newstat = "Gathering MACs on switch %s of %s..." % (onhost, hostcount)
stat.set(newstat)
#canvas.update()
conn.execute('term len 0') #We don't want a pause in the output
conn.execute('sh ver') #Make sure we're a Nexus or Catalyst switch before continuing
r = conn.response
if r != '' and ('nx-os' or 'catalyst') in r.lower():
#Grab the device's hostname:
As soon as it hits the "stat.set(newstat)" line it hangs. What am I doing wrong? Sorry if I slaughtered all the programmer lingo, I'm a network engineer pretending to be a programmer. Thanks!
Related
Good afternoon thank you all who look at my issue,
I am building a script to do initial configuration on switches through console port, I can make a connection to the console port the issue comes when building a GUI in TKINTER.
I want the gui to connect through a specified some port when the button is pressed and when the connection is made make a light(using canvas) go green showing a successful connection went through.
My issue is getting the light to change to green and stay when a connection is made I have tried
my initally also tried to use global then realized the window loop constantly set it back to red
nested while loop--- breaks Tkinker
importing a file I created called variableset which stores variable to set green but since it constantly has a new instance it just sets the variable in tinker back to red.
any help would be greatly appreciated
GUI CODE
import tkinter as tk
from tkinter import *
import connect
import variableset
setting = variableset.setting
window = tk.Tk()
window.title("Network Wizard 1")
window.geometry('300x500')
# setting =tk.IntVar()
# setting.set(variableset.setting)
#serial port pick the right one
serialportlabel = tk.Label(text="COM Number")
serialport = tk.Entry(width= 7)
serialportlabel.pack()
serialport.pack()
#color alerting!! red bad
alert = Canvas(window, width=50, height=20)
alert.pack()
if setting == 0:
a=alert.create_rectangle(5, 0, 50, 50, fill='red')
else:
a=alert.create_rectangle(5, 0, 50, 50, fill='green')
# #connection part woop woop
connection = tk.Button(text="connect", command = lambda: bus())
connection.pack()
# #firmwarecheck
# firmwarecheck = tk.Button(text="Firmware check")
# firmwarecheck.pack()
# #firmware update
# firmwareupdate= tk.Button(text="Firmware update")
# firmwareupdate.pack()
# #software update
# software = tk.Button(text="software update")
# connect.pack()
# #vmlans
# vlanupdate = tk.Button(text="Vlanupdate")
# vlanupdate.pack()
# disconnect = tk.Button(text="disconnect")
# disconnect.pack()
# qut = tk.Button(text="quite")
# qut.pack()
def bus():
global ser
ser = connect.connect(serialport.get())
global setting
setting = variableset.initial
# def firmware(ser):
# if ser.isOpen() == true :
window.mainloop()
Connect code
import serial
import time
import sys
#connect
def connect(com):
ser = serial.Serial(
port = com, #COM
baudrate=9600,
parity='N',
stopbits=1,
bytesize=8,
timeout=8
)
ser.isOpen()
print(ser.name)
#set variables
enter = str.encode('\r\n') #enter
user = str.encode('admin#sytem\r\n') #default user name
pwd = str.encode('\r\n') #defualt password
qut = str.encode('quit\r')
time.sleep(1.0)
# ser.inWaiting()
ser.write(enter) #promt login
time.sleep(0.5)
ser.write(user) #enter user name
time.sleep(0.5)
ser.write(pwd) #enter password
time.sleep(0.5)
ser.write(enter)
time.sleep(0.5)
ser.write(enter)
time.sleep(0.5)
ser.write(str.encode("sytem\r\n"))
time.sleep(0.5)
ser.write(qut)
ser.write(qut)
input_read = ser.read(500)
input_read = input_read.decode("utf-8","ignore")
print(input_read)
ser.close()
return ser
def write(ser):
ser.write(str.encode(''+'\r\n'))
def disconnect(ser):
ser.write(str.encode('quit\r\n'))
time.sleep(.2)
ser.write(str.encode('quit\r\n'))
time.sleep(.2)
ser.write(str.encode('quit\r\n'))
time.sleep(.2)
ser.close()
Variableset Code
global setting
setting = 0
def initial():
global setting
setting = 1
return setting
def unset():
global setting
setting = 0
return setting
Any help greatly appreciated
Your if-else statement only runs once, before the setting variable has been set, as far as we can tell. It's not in a loop or a function that gets called from somewhere else. What exactly is the problem you're having with the code that you posted? The issues you described seem to relate to a loop setting it back to red in a different version of the code.
In any event, I suspect that your problem is trying to use infinite loops to keep checking the setting, which prevents the tkinter mainloop() from running, and therefore blocks the GUI from updating. If so, the best method to fix it is probably to change your code for "check the setting and then change the color" into its own function, which gets called for the first time at the end of your connect function. Then, the end of the check setting function should schedule itself to be run after a time delay using the tkinter after() method, which is non-blocking (asnychronous) and allows the mainloop() to keep running. Alternative methods include a separate thread to run the check-setting code, or using the tkinter update() method, but after() is easiest.
Note that your sleep() functions in the connect code will also block the mainloop while they're running. I'm also not clear what you're trying to do with the variableset code; it appears to set the setting to 1 (aka green light) when you call initial(), regardless of the actual status on the serial port.
I am trying to run a GUI that spawns threads that perform very basic and not computationally complicated tasks on a raspberry pi 1 and I cannot seem to get the threads to work.
I developed the code on a x86 intel computer and it works perfectly well. The threading commands basically just allow for button presses and listening for serial data concurrently.
def extra_thread_disable():
# Disables buttons that would interfere with data that is currently being sent
while threading.active_count() == 3:
run_file_butt.config(state = 'disabled')
run_butt.config(state = 'disabled')
serial_butt.config(state = 'disabled')
popup_butt.config(state = 'disabled')
homing_butt.config(state = 'disabled')
level_butt.config(state = 'disabled')
zero_button1.config(state = 'disabled')
zero_button2.config(state = 'disabled')
zero_button3.config(state = 'disabled')
else:
run_file_butt.config(state = 'normal')
run_butt.config(state = 'normal')
serial_butt.config(state = 'normal')
popup_butt.config(state = 'normal')
homing_butt.config(state = 'normal')
level_butt.config(state = 'normal')
zero_button1.config(state = 'normal')
zero_button2.config(state = 'normal')
zero_button3.config(state = 'normal')
pass
def thread_data():
# Starts a thread to send data while allowing stop button to be pressed
try:
global t2
t2 = threading.Thread(name='send_line', target = send_data, daemon = True)
t_disable = threading.Thread(name='disable', target = extra_thread_disable, daemon = True)
t2.start()
t_disable.start()
except:
update_textbox("Threading Error: data thread not properly created")
def send_data():
# Sends single motion commands and waits for response to continue
global save_path
global motor_param
vals = get_vals()
try:
data = struct.pack("!llllhhhhhhhh", vals['dist1'], vals['dist2'], vals['dist34'], vals['dist34'], vals['speed1'], vals['speed2'], vals['speed34'], vals['speed34'], vals['accel1'], vals['accel2'], vals['accel34'], vals['accel34'])
try:
ser.write(data)
update_textbox("Running...")
except:
update_textbox("Error: Data not sent")
try:
motor1pos = int(ser.readline())
motor2pos = int(ser.readline())
motor3pos = int(ser.readline())
motor4pos = int(ser.readline())
ready = ser.read(1)
update_textbox("Movement complete")
axis1_current.set(str(reverse_convert(motor1pos, 1)))
axis2_current.set(str(reverse_convert(motor2pos, 2)))
axis3_current.set(str(reverse_convert(motor3pos, 3)))
writetofile()
except:
update_textbox("Error: reading data improperly")
except:
update_textbox("Error: data not sent properly")
pass
The code basically just allows the main GUI thread to allow for a stop button to be pressed and disable all the buttons that could interfere with the sent data. That thread then just waits for the response from an arduino it is connected to. Again this all works flawlessly on a normal computer. I get no errors or warnings in the terminal when run on a raspberry pi but it seems to be blocking. I thought maybe it was just such a slow computer or the infamous GIL. It seems like that might be the reason. If so, should I switch to the multiprocessing library in python? is there a way to get around this? It doesn't work when run in terminal calling python3 and it doesn't work when it was compiled to a static binary using pyinstaller.
The answer is to get better hardware. I changed nothing in my code but instead bought a newer raspberry pi 4 instead of the original raspberry pi and the threading worked as it originally did on my other pc running arch.
Basically, I am asking how to put a continuously updating program on display into tkinter's text widget.
from tkinter import Tk, Frame, Text, BOTH
class FrameApp(Frame):
def __init__(self, parent):
Frame.__init__(self, parent, background="white")
self.parent = parent
self.parent.title("Ethis")
self.pack(fill=BOTH, expand=1)
self.centerWindow()
def centerWindow(self):
w = 900
h = 450
sw = self.parent.winfo_screenwidth()
sh = self.parent.winfo_screenheight()
x = (sw - w)/2
y = (sh - h)/2
self.parent.geometry("%dx%d+%d+%d" % (w, h, x, y))
def theText(self):
w = Text ()
def main():
root=Tk()
app = FrameApp(root)
root.mainloop()
if __name__ == '__main__':
main()
This is my tkinter program. As you can see, I've centered it and set it up with a text function defined as theText(self). I have done anything with theText(self) because I don't know where to begin. This works fine alone, it starts up, as expected, in the center with it's title.
# Money Generator Mark 1
import time
t = 'true'
while t == 'true':
s = 0
x = 1
print ("You have $%s." % (s))
time.sleep(.75)
t = 'false'
while t == 'false':
s = s + (1 * x)
print ("You have $%s." % (s))
time.sleep(.75)
if s >= 100 and s < 200:
x = 2
if s >= 200:
x = 4
Here I have another program that works fine on it's own. I've dubbed it Money Generator as is akin to Cookie Clicker and Candy Box, those types of things. This also works fine in the command box, function and printing to there. I was wondering how to integrate these two separate programs so that the second program that is listed here will be displayed in tkinter's window.
Here is my new code that has a new issue. I'm receiving an error stating that 'generate_money' is not defined in the theText function. These new functions are within my frameApp class.
def theText(self):
self.w = Text()
self.t = threading.Thread(target=generate_money, args=(self.w))
self.t.daemon = True
def generate_money(textwidget):
p = subprocess.Popen([sys.executable, os.path.join('window.py', 'moneygenerator.py')],
stdout = subprocess.PIPE)
for line in p.stdout:
do_stuff_with(textwidget, line)
p.close()
This is unfortunately going to be a little trickier than you'd like.
The first part is easy: you can just use the subprocess module to run the background script and capture its output:
p = subprocess.Popen([sys.executable, os.path.join(scriptpath, 'moneygenerator.py')],
stdout = subprocess.PIPE)
for line in p.stdout:
do_stuff_with(line)
p.close()
The problem is that doing this in the middle of a Tkinter callback will block the entire program until the background program is done. So, instead of getting updated on each line, you'll just freeze up the app until the OS kills you/displays a beachball/etc.
There are two usual solutions to doing stuff without blocking: Do it on a thread—which is easy, except that you can't access Tkinter widgets from a background thread. Or check the subprocess's pipe in a non-blocking way—which would be great if there were a cross-platform way to do that without potentially blocking forever. (There are, however, third-party "async subprocess" wrappers on PyPI, which may be an alternate way to solve this problem.)
So, you need to combine both of these. Have a thread that blocks on the subprocess and posts messages on something that you can check in a non-blocking way in the main thread, like a queue.Queue.
But, instead of writing it yourself, there's a nice wrapper called mtTkinter that does most of the hard part for you. You can just write your background thread as if it were legal to access the widgets, and it will intercept that access and turn it into queue posts. Sadly, mtTkinter doesn't work in Python 3—but it looks pretty easy to fix; I slapped together a port in a few minutes. Although I haven't done much testing on it, I think this is probably the simplest way forward.
So:
from tkinter import Tk, Frame, Text, BOTH
import subprocess
import sys
import threading
# ...
def generate_money(textwidget):
p = subprocess.Popen([sys.executable, os.path.join(scriptpath, 'moneygenerator.py')],
stdout = subprocess.PIPE)
for line in p.stdout:
do_stuff_with(textwidget, line)
p.close()
# ...
class FrameApp(Frame):
# ...
def theText(self):
self.w = Text()
self.t = threading.Thread(target=generate_money, args=(self.w,))
You'll probably want to add some way to either tell self.t to shut down early, or just wait for it to finish, at quit time. (Or, alternatively, because you know that it can't leave any "dangerous garbage" behind it killed suddenly, you may be able to just set self.t.daemon = True.) But this is enough to show the basics.
I have create a code which opens a serial port where is an arduino connected and takes some data,so far so good,my problem is when I open my program and press the start button (which opens the serial port and wait receive the data from arduino) and I have forget for some reason to connect the arduino then obviously it doesn't do anything, when I plug it without close and open it again the program and press the start still it doesn't work.Obvious if I restart the program there is no issue but how can I solve this without restarting the program?My second issue is when my program works and receiving data and I disconnect the USB my program it doesn't close even if I press stop and try to close or if I connect it again the USB and afterwards try to close it,the only way to close it is from the Task manager.Why it happens this and how can I solve it?For the second issue I can't understand why when I close the program from the task manager and run it again still it dosn't work,works only if I unplug the USB and connect it again and rerun the program.
P.S: I know there is no obvious reasons to do such things but I'm curious and I wont to learn.
Here is the code from the start/stop button:
def onStartStopButton(self, event):
global a2
global q
global i
if not self.isLogging:
self.isLogging = True
self.ser = serial.Serial()
self.ser.baudrate = 38400
self.ser.timeout=0.25
# Try serial ports one by one starting
for m in range(29, 0, -1):
self.ser.port = m
try:
self.ser.open()
break
except:
# We end up here if this port number
# failed to open
pass
if self.ser.isOpen():
i=0
# We successfully opened a port, so start
self.timer.IsRunning()
self.t=time.time() -self.tZero
self.tZero=time.time()
self.timer.Stop()
self.timer.Start(100)
self.startbtn.SetLabel("Stop")
else:
i=1
self.timer.Stop()
self.ser.close()
self.isLogging = False
self.startbtn.SetLabel("Start")
a2 = True
def GetSample(self,event=None):
global a2
global a4
global a5
global b1
global b2
global b7
global h
global q
global arduinoDelay
global last_received1
global now
global array
a1=0
a3=0
self.t=time.time() -self.tZero
tiTuple=time.gmtime(self.t)
reste=self.t-tiTuple[3]*3600.0-tiTuple[4]*60.0-tiTuple[5]*1.0
resteS=("%.2f" % reste )[-2::]
tt=time.strftime("%H:%M:%S",tiTuple)
self.tAff.SetLabel(tt)
while a2:#send to arduino time refresh
for a1 in range (2):
self.ser.write(arduinoDelay)
time.sleep(0.5)
h=0
print "write to arduino delay ",arduinoDelay
a2=False
arduinoSensorNum = 'E'
if '1' in sensor_select:
arduinoSensorNum += '1'
if '2' in sensor_select:
arduinoSensorNum += '2'
if '3' in sensor_select:
arduinoSensorNum += '3'
if '4' in sensor_select:
arduinoSensorNum += '4'
if '5' in sensor_select:
arduinoSensorNum += '5'
if '6' in sensor_select:
arduinoSensorNum += '6'
arduinoSensorNum += '/'
a4=True
if (sensor_select == ''):#if no one sensor select--> enable all
arduinoSensorNum = 'E123456/'
while a4:#send arduino enable num sensors
for a3 in range (1):
self.ser.write(arduinoSensorNum)
time.sleep(0.5)
print "write to arduino sensor Num ",arduinoSensorNum
a4=False
if not h==1:
q=1
self.ser.write('S')
print "send S "
sample_string = self.ser.readline()
now = datetime.datetime.now()
if len(sample_string) == 15:#
sample_string = sample_string[0:-1]
sample_values = sample_string.split()
for m in range(self.M):
# get one value from sample
value = int(sample_values[m])
self.x[m][0:99] = self.x[m][1:]
self.x[m][99] = value
# Update plot
self.ax.cla()
self.ax.autoscale(False)
self.ax.set_xlim(0, self.N - 1)
self.ax.set_ylim(-100, 1100)
for m in range(self.M):
self.ax.plot(self.n, self.x[m])
self.canvas.draw()#
if not sample_string =='':
h=1#stop send start to arduino
if not b2 == True:
file_date = open("C:/TMS Data/date.txt","a")
file_date.write(now.strftime("%Y-%m-%d\n"))
file_date.write(now.strftime("%H:%M:%S\n"))
print "######################"
b2 = True
file_sensors = open("C:/TMS Data/sensors.txt","a")
file_sensors.write(sample_string)
print q
array[q]=sample_string
print array[q]
q=q+1
"""if q == 7 and i==1:
self.timer.Stop()
self.ser.close()
self.isLogging = False
#self.startbtn.SetLabel("Start")
a2 = True"""
if q == 7:
q=1
b2 = False
b7=True
if b7 == True:
self.textctrl0.Clear()
self.textctrl0.AppendText(array[1])
self.textctrl1.Clear()
self.textctrl1.AppendText(array[2])
self.textctrl2.Clear()
self.textctrl2.AppendText(array[3])
self.textctrl3.Clear()
self.textctrl3.AppendText(array[4])
self.textctrl4.Clear()
self.textctrl4.AppendText(array[5])
self.textctrl5.Clear()
self.textctrl5.AppendText(array[6])
b7=False
Ok I figured it out, the problem was the part with the quotes, the use of this part was when the user pressed the stop button the code continued until take the last value from the last sensor. The above problems was solved when I add this lines in onStartButtons define!
My new problem now is that if the user press stop and the arduino was sending the value from the sensor 3 how can I make it continue and take the other 3 values from sensors 4-5-6 and then to close the serial communication?
If the calls to your serial code are making your program unresponsive, then the serial call is likely blocking the GUI's mainloop. In that case, you'll want to do all your serial work in a separate thread and use wxPython's thread-safe methods to update the display. Those methods are wx.CallAfter, wx.CallLater, and wx.PostEvent. You can read more about wxPython and threading at the following:
http://wiki.wxpython.org/LongRunningTasks
http://www.blog.pythonlibrary.org/2010/05/22/wxpython-and-threads/
If you do attempt to call a wxPython method from a thread, the behavior will be undefined and you may or may not have immediate issues.
For serial issues, I would recommend reading the library's documentation and/or contacting their mailing list or dev team with questions/issues.
It sounds to me like this is an example of an old limitation regarding UNIX serial device access (one which was inherited by Linux). There are not OS native primitives preventing contention on the devices. If multiple processes open a serial device under Linux or UNIX then, generally, the they will always be racing for any input from the device (characters will be non-deterministically received by one or another process) and output will sporadically interleaved and blocked for all processes writing to the device.
Classically UNIX and Linux programs use locking files (such as /var/log/LCK..ttyS0) to co-ordinate access to these devices. This is explained in places like: Linux Serial HOWTO: Lockfiles and has been discussed a few times on StackOverflow Stack Overflow: How can you access a serial port from two different processes (Python)
Serial Communication one to one
I'm using Tkinter.askopenfilename to open a file that contains logon details for a peripheral device. My code takes those details and then logs into that device, navigates through a CLI and downloads a configuration image from the peripheral.
This process can take 10 - 15 seconds and I'd like to change the cursor status during this period. I've found that if I try and open a new window to show an in progress message, the download completes before the dialog is displayed.
Sample code fragment:
filename = askopenfilename(parent=root,filetypes=[("Configured Devices",".cfg")])
if len(filename) == 0:
return
file_list = open(filename,'r')
for line in file_list:
line=line.strip()
line=line.split(",")
ip=line[0]
username = line[1]
password = line[2]
break
file_list.close()
get_config(ip,username,password) #This logs in and extracts the configuration
I have struggled with the same thing. Not able to find answers that I like and seemed complete. So here is an example I put together:
import tkinter as Tk
import time
class AppWaitCursor( ):
"""
Purpose:
display wait cursor, continue with a blocking task
and restore cursor
Here: task is a sleep and print
This is a bit of a pain as we need 2 methods to implement, could get cleverer
passing around functions, perhaps closures, partials, or lambdas, that would
obscure the technique.
Got something better for the same purpose? Let me know.
works but not reliably inside of spyder --
run from command line
only tried in windows python 3.8
"""
# ----------- init -----------
def __init__( self, ):
self.build_run() # separate method for easier messing about
# ------------------------------------------
def begin_wait_cursor_task( self, ):
"""
this really just sets up the cursor, then calls the rest of the task
"""
self.root.config( cursor="wait" ) # but not displayed need a refresh
print( "begin_wait_cursor_task", flush = True)
# time in ms 20 seems good 2 not so much, idle did not do it for me:
# this may give the user 20 ms to interact with gui but most will not notice
# click twice fast and it may then run twice!
self.root.after( 20 , self.continue_wait_cursor_task )
# ------------------------------------------
def continue_wait_cursor_task( self ):
"""
"""
print( "continue_wait_cursor_task", flush = True)
time.sleep( 1 )
print( "almost there" )
time.sleep( 1 )
self.root.config( cursor="" )
print( "normal cursor\n\n", flush = True)
# ----------------------------------------
def build_run( self):
"""
build the gui and run it
"""
self.root = Tk.Tk()
a_widget = Tk.Button( self.root, text="do wait cursor task", command = self.begin_wait_cursor_task )
a_widget.pack()
a_widget = Tk.Button( self.root, text="do nothing", )
a_widget.pack()
self.root.mainloop()
def ex_cursor0():
print( """
------------------ ex_cursor0 ----------------
""" )
app = AppWaitCursor( )
ex_cursor0()
enter code here
You need to use root.config(cursor="wait") to change the cursor to a busy cursor.