This post will walk you through creating a Win Form (GUI) application in Python with TKinter. For this article, Visual Studio 2015 with the Python Add-in was used. You can also use Visual Studio 2017 or other compatible IDEs.
Featured in this article:
Discover a USB device.
Flash the LED of the device.
Detect the number of channels on the device.
Detect the supported analog input ranges on the device.
Read the voltage of an analog input channel.
Display the acquired value on the screen.
Implement a timer to read from the device repeatedly until the user clicks a Stop button.
This article assumes you have installed Python 3.6 (this examples uses Python 2.7), tKinter, InstaCal, and installed the mcculw Python library from github (https://github.com/mccdaq/mcculw). It also assumes you have some working knowledge of Python and its program structures.
This example focuses on device discovery and single point analog input with the USB-1408FS-Plus or USB-1408FS device. However, it can be easily modified to use any Measurement Computing device that has an analog input. It can also be modified to access the digital IO, counter, or analog output features of a device.
Let’s get started!
Setting up a New Project
To begin, launch Visual Studio, and start a new project. The new project dialog box appears:
Select Python as the language, and then select Python Application.
Enter an appropriate name (this example uses the name Py_JG02), and click OK.
After the system finishes creating the solution, go to the Solution Explorer window, right click on Search Paths, and select “Add Folder to Search Path…” from the menu.
When the Select folder to add to search path dialog appears, navigate to C:\Users\Public\Documents\Measurement Computing\DAQ, click Python, and click “Select Folder.”
Go to the solution Explorer window, right click on the project name, and select “Add”, “Existing folder…”.
When the select existing folder dialog box appears, navigate back to C:\Users\Public\Documents\Measurement Computing\DAQ\Python, select “mcculw” with a single mouse click to highlight it, and click “Select folder.”
Your Solution Explorer will look like this:
Creating the Project
We are now ready to create the project. The first thing we need to do is import support libraries. In the code window, add the following to add ‘links’ to tkinter and the Universal Library:
from tkinter import *
from builtins import *
from tkinter.ttk import Combobox
from mcculw import ul
from mcculw.enums import *
from mcculw.examples.ui.uiexample import UIExample
from mcculw.examples.props.ai import *
from mcculw.ul import ULError
import tkinter as tk
class Py_JG02(UIExample):
def __init__(self, master):
super(Py_JG02, self).__init__(master)
# Start the example if this module is being run
if __name__ == "__main__":
#Start the example
Py_JG02(master=tk.Tk()).mainloop()
That is all the boiler plate code you need!
Setting Up the UI
Before we get into the Universal Library calls, I thought it would be best to set up the user interface. By adding the user interface syntax first, it should make reading the library code calls easier to understand and more clear since you will have a more complete picture of the process. Unlike Visual Basic.NET or C#, the code to add these objects must be created manually, but it’s not as bad as you think. Creating the section of the code is pretty clear once you understand how it works. Here is the code and some useful comments (it is located just before the last bits of code from above):
def create_widgets(self):
#'''Create the tkinter UI'''
main_frame = tk.Frame(self) #Create the main code frame
main_frame.pack(fill=tk.X, anchor=tk.NW)
curr_row = 0
device_id_left_label = tk.Label(main_frame) #I wanted to add the device ID
device_id_left_label["text"] = "Device Identifier: " #This is its label
device_id_left_label.grid(row=curr_row, column=0, sticky=tk.W, padx=3, pady=3)
self.device_id_label = tk.Label(main_frame)
self.device_id_label["text"] = self.device.unique_id #this is the device’s ID
self.device_id_label.grid(row=curr_row,column=1, sticky=tk.W, padx=3, pady=3)
curr_row += 1
channel_entry_label = tk.Label(main_frame) #add a label for channel number select
channel_entry_label["text"] = "Channel Number:"
channel_entry_label.grid(row=curr_row, column=0, sticky=tk.E)
channel_vcmd = self.register(self.validate_channel_entry)
# Call validate Channel number (make sure it is a number and within range of the device.
self.channel_entry = tk.Spinbox( #I wanted to use a spin button.
main_frame, from_=0, #this is how to add it.
to=max(self.ai_props.num_ai_chans- 1, 0),
validate='key',validatecommand=(channel_vcmd, '%P'))
self.channel_entry.grid(row=curr_row,column=1, sticky=tk.W)
curr_row += 1
range_label = tk.Label(main_frame) #For user selected voltage range
range_label["text"] = "Range:"
range_label.grid(row=curr_row, column=0, sticky=tk.E)
self.range_combobox = Combobox(main_frame) #Combobox to hold supported ranges
self.range_combobox["state"] = "readonly"
self.range_combobox["values"] = [
x.name for x in self.ai_props.available_ranges]
self.range_combobox.current(0)
self.range_combobox.grid(row=curr_row,column=1, padx=3, pady=3)
curr_row += 1
value_left_label = tk.Label(main_frame) #A label to tell us where the reading is
value_left_label["text"] = (
"Value read from selected channel(V):")
value_left_label.grid(row=curr_row, column=0, sticky=tk.W)
self.value_label = tk.Label(main_frame) #label to hold the measured value
self.value_label.grid(row=curr_row,column=1, sticky=tk.W)
button_frame = tk.Frame(self) #Add a frame to hold the Start/Stop & quit buttons
button_frame.pack(fill=tk.X, side=tk.RIGHT, anchor=tk.SE)
self.start_button = tk.Button(button_frame) #Start/Stop button
self.start_button["text"] = "Start"
self.start_button["command"] = self.start
self.start_button.grid(row=0,column=0, padx=3, pady=3)
self.quit_button = tk.Button(button_frame) #Quit button
self.quit_button["text"] = "Quit"
self.quit_button["command"] = self.master.destroy
self.quit_button.grid(row=0,column=1, padx=3, pady=3)
Device Discovery
We can now add device discovery code to access the Measurement Computing device (the USB-1408FS-PLUS or USB-1408 in this case).
By implementing device discovery, InstaCal is not needed prior to running the device. Just plug in the device, let the Windows OS discover it, and run the program. Here is the syntax needed for device discovery:
self.board_num = 0 #create constant for board 0
ul.ignore_instacal() #don’t use InstaCal board config file
if self.discover_devices(): #call discover_devices below. If any boards are found
self.create_widgets() #load the widgets for the screen
else:
self.create_unsupported_widgets(self.board_num)
def discover_devices(self):
#Get the device inventory
devices = ul.get_daq_device_inventory(InterfaceType.USB) #get device inventory
self.device = next((device for device in devices #find one with 1408
if "1408" in device.product_name), None) #in the name
if self.device != None: #if it is found,
ul.create_daq_device(self.board_num, self.device) #create an instance
ul.flash_led(self.board_num) #flash the LED, and
self.ai_props = AnalogInputProps(self.board_num) #get its properties
return True
return False
The above code gets placed right after this code:
class Py_JG01(UIExample):
def __init__(self, master):
super(Py_JG01, self).__init__(master)
Analog Input
Now, we will add the code to:
Detects the selected range.
Detects the selected analog input channel number.
Validates the selected channel (make sure it is a number and within the channel range of the device).
Read the voltage value from selected channel using the V_In function of UL.
Repeat this operation every 100 ms.
This section of code goes right after the code we entered above:
def get_range(self): #Read the voltage range from the combobox
selected_index = self.range_combobox.current()
return self.ai_props.available_ranges[selected_index]
def get_channel_num(self): #Read the channel value
try:
return int(self.channel_entry.get())
except ValueError:
return 0
def validate_channel_entry(self, p): #make sure the value really is a valid number
if p == '':
return True
try:
value = int(p)
if(value < 0 or value > self.ai_props.num_ai_chans - 1):
return False
except ValueError:
return False
return True
def update_value(self): #Read the analog voltage value
try:
# Read voltage value from a channel
channel = self.get_channel_num() #from function above
ai_range = self.get_range() #from function above
value = ul.v_in(self.board_num, channel,ai_range) #call V_In from the UL
# Display the raw value
self.value_label["text"] = '{:.4f}'.format(value) #display the voltage read
if self.running:
self.after(100, self.update_value) #this is the timer to repeat 100 mS
except ULError as e:
self.show_ul_error(e)
Starting and Stopping the Operation
Since we want to have a Start and Stop button to start and stop the operation, we will add the routines for those next:
Users can select the analog input channel to read and the range of the analog input channel. When the Start button is pressed, the value of the selected channel is returned every 100 ms until the user presses the Stop button or exits the application by pressing Quit.
Question
Jeffrey
This post will walk you through creating a Win Form (GUI) application in Python with TKinter. For this article, Visual Studio 2015 with the Python Add-in was used. You can also use Visual Studio 2017 or other compatible IDEs.
Featured in this article:
This article assumes you have installed Python 3.6 (this examples uses Python 2.7), tKinter, InstaCal, and installed the mcculw Python library from github (https://github.com/mccdaq/mcculw). It also assumes you have some working knowledge of Python and its program structures.
This example focuses on device discovery and single point analog input with the USB-1408FS-Plus or USB-1408FS device. However, it can be easily modified to use any Measurement Computing device that has an analog input. It can also be modified to access the digital IO, counter, or analog output features of a device.
Let’s get started!
Setting up a New Project
To begin, launch Visual Studio, and start a new project. The new project dialog box appears:
Your Solution Explorer will look like this:
Creating the Project
We are now ready to create the project. The first thing we need to do is import support libraries. In the code window, add the following to add ‘links’ to tkinter and the Universal Library:
from tkinter import * from builtins import * from tkinter.ttk import Combobox from mcculw import ul from mcculw.enums import * from mcculw.examples.ui.uiexample import UIExample from mcculw.examples.props.ai import * from mcculw.ul import ULError import tkinter as tk class Py_JG02(UIExample): def __init__(self, master): super(Py_JG02, self).__init__(master) # Start the example if this module is being run if __name__ == "__main__": #Start the example Py_JG02(master=tk.Tk()).mainloop()
That is all the boiler plate code you need!
Setting Up the UI
Before we get into the Universal Library calls, I thought it would be best to set up the user interface. By adding the user interface syntax first, it should make reading the library code calls easier to understand and more clear since you will have a more complete picture of the process. Unlike Visual Basic.NET or C#, the code to add these objects must be created manually, but it’s not as bad as you think. Creating the section of the code is pretty clear once you understand how it works. Here is the code and some useful comments (it is located just before the last bits of code from above):
def create_widgets(self): #'''Create the tkinter UI''' main_frame = tk.Frame(self) #Create the main code frame main_frame.pack(fill=tk.X, anchor=tk.NW) curr_row = 0 device_id_left_label = tk.Label(main_frame) #I wanted to add the device ID device_id_left_label["text"] = "Device Identifier: " #This is its label device_id_left_label.grid(row=curr_row, column=0, sticky=tk.W, padx=3, pady=3) self.device_id_label = tk.Label(main_frame) self.device_id_label["text"] = self.device.unique_id #this is the device’s ID self.device_id_label.grid(row=curr_row,column=1, sticky=tk.W, padx=3, pady=3) curr_row += 1 channel_entry_label = tk.Label(main_frame) #add a label for channel number select channel_entry_label["text"] = "Channel Number:" channel_entry_label.grid(row=curr_row, column=0, sticky=tk.E) channel_vcmd = self.register(self.validate_channel_entry) # Call validate Channel number (make sure it is a number and within range of the device. self.channel_entry = tk.Spinbox( #I wanted to use a spin button. main_frame, from_=0, #this is how to add it. to=max(self.ai_props.num_ai_chans- 1, 0), validate='key',validatecommand=(channel_vcmd, '%P')) self.channel_entry.grid(row=curr_row,column=1, sticky=tk.W) curr_row += 1 range_label = tk.Label(main_frame) #For user selected voltage range range_label["text"] = "Range:" range_label.grid(row=curr_row, column=0, sticky=tk.E) self.range_combobox = Combobox(main_frame) #Combobox to hold supported ranges self.range_combobox["state"] = "readonly" self.range_combobox["values"] = [ x.name for x in self.ai_props.available_ranges] self.range_combobox.current(0) self.range_combobox.grid(row=curr_row,column=1, padx=3, pady=3) curr_row += 1 value_left_label = tk.Label(main_frame) #A label to tell us where the reading is value_left_label["text"] = ( "Value read from selected channel(V):") value_left_label.grid(row=curr_row, column=0, sticky=tk.W) self.value_label = tk.Label(main_frame) #label to hold the measured value self.value_label.grid(row=curr_row,column=1, sticky=tk.W) button_frame = tk.Frame(self) #Add a frame to hold the Start/Stop & quit buttons button_frame.pack(fill=tk.X, side=tk.RIGHT, anchor=tk.SE) self.start_button = tk.Button(button_frame) #Start/Stop button self.start_button["text"] = "Start" self.start_button["command"] = self.start self.start_button.grid(row=0,column=0, padx=3, pady=3) self.quit_button = tk.Button(button_frame) #Quit button self.quit_button["text"] = "Quit" self.quit_button["command"] = self.master.destroy self.quit_button.grid(row=0,column=1, padx=3, pady=3)
Device Discovery
We can now add device discovery code to access the Measurement Computing device (the USB-1408FS-PLUS or USB-1408 in this case).
By implementing device discovery, InstaCal is not needed prior to running the device. Just plug in the device, let the Windows OS discover it, and run the program. Here is the syntax needed for device discovery:
self.board_num = 0 #create constant for board 0 ul.ignore_instacal() #don’t use InstaCal board config file if self.discover_devices(): #call discover_devices below. If any boards are found self.create_widgets() #load the widgets for the screen else: self.create_unsupported_widgets(self.board_num) def discover_devices(self): #Get the device inventory devices = ul.get_daq_device_inventory(InterfaceType.USB) #get device inventory self.device = next((device for device in devices #find one with 1408 if "1408" in device.product_name), None) #in the name if self.device != None: #if it is found, ul.create_daq_device(self.board_num, self.device) #create an instance ul.flash_led(self.board_num) #flash the LED, and self.ai_props = AnalogInputProps(self.board_num) #get its properties return True return False
The above code gets placed right after this code:
class Py_JG01(UIExample): def __init__(self, master): super(Py_JG01, self).__init__(master)
Analog Input
Now, we will add the code to:
This section of code goes right after the code we entered above:
def get_range(self): #Read the voltage range from the combobox selected_index = self.range_combobox.current() return self.ai_props.available_ranges[selected_index] def get_channel_num(self): #Read the channel value try: return int(self.channel_entry.get()) except ValueError: return 0 def validate_channel_entry(self, p): #make sure the value really is a valid number if p == '': return True try: value = int(p) if(value < 0 or value > self.ai_props.num_ai_chans - 1): return False except ValueError: return False return True def update_value(self): #Read the analog voltage value try: # Read voltage value from a channel channel = self.get_channel_num() #from function above ai_range = self.get_range() #from function above value = ul.v_in(self.board_num, channel,ai_range) #call V_In from the UL # Display the raw value self.value_label["text"] = '{:.4f}'.format(value) #display the voltage read if self.running: self.after(100, self.update_value) #this is the timer to repeat 100 mS except ULError as e: self.show_ul_error(e)
Starting and Stopping the Operation
Since we want to have a Start and Stop button to start and stop the operation, we will add the routines for those next:
def stop(self): self.running = False self.start_button["command"] = self.start self.start_button["text"] = "Start" def start(self): self.running = True self.start_button["command"] = self.stop self.start_button["text"] = "Stop" self.update_value()
And that’s it.
Running the Application
To launch the app, from the IDE menu, click Start
The running app:
As you can see, the ID of the MCC is displayed.
Users can select the analog input channel to read and the range of the analog input channel. When the Start button is pressed, the value of the selected channel is returned every 100 ms until the user presses the Stop button or exits the application by pressing Quit.
Additional Information Resources
On TKinter from Python.org:https://wiki.python.org/moin/TkInter
Tkinter 8.5 reference: a GUI for Python: http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/index.html
Go to YouTube.com, and search on “thenewboston python”
Py_JG02.zip
Link to comment
Share on other sites
1 answer to this question
Recommended Posts