Jump to content
  • 0

Step-By-Step Python in Visual Studio and the UL on Windows


Jeffrey

Question

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:

image.png.15dd0967e434d9b8a1ce6c685fac688e.png

  1.           Select Python as the language, and then select Python Application.
  2.          Enter an appropriate name (this example uses the name Py_JG02), and click OK.
  3.          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.
  4.          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.”
  5.          Go to the solution Explorer window, right click on the project name, and select “Add”, “Existing folder…”. 
  6.          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:

image.png.8442e42521bd96d803cddde35b995566.png

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:

    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:

image.png.71dfe556b9f5d3a689d7ea5c51f5bf9b.png

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

Guest
This topic is now closed to further replies.
×
×
  • Create New...