Measurement Computing   Easy to Use | Easy to Integrate | Easy to Support catalog banner

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

Expand / Collapse
 

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


This KB article 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 2.7 or 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:


  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.”

Now, 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:

    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”



Rate this Article:
Tags:

Attachments


Py_JG02.zip Py_JG02.zip (556.44 KB, 1,456 views)

Add Your Comments


For comments email [email protected].

Details
Article ID: 50736

Last Modified:11/28/2017 2:41:35 PM

Article has been viewed 25,616 times.

Options