LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

Running two Python Coded simultaneously

Solved!
Go to solution

Hello everyone,

 

So I am new to LabView and I would like to run simultaneously these two Python nodes but I have noticed that one runs and finishes before the other one does.

 

How do I rectify this?

 

Thanks Smiley Happy

0 Kudos
Message 1 of 11
(251 Views)

I have to get this project done really soon and I would be glad if you could offer your help... Thanks Smiley Happy

0 Kudos
Message 2 of 11
(202 Views)

You probably can't.  My guess is that this function is not reentrant.

I don't know the details about this new python node, but I can believe it is calling a python runtime engine, and there is only one instance of those that can run.  Thus the first node to get called blocks the other until it frees up the python resource.

0 Kudos
Message 3 of 11
(192 Views)

Labview claims here that Python nodes can run in parallel even when you call the same function from the same module in the two nodes

 

https://knowledge.ni.com/KnowledgeArticleDetails?id=kA00Z000001DcBbSAK&l=en-US

 

If there was a miscommunication in the FAQ answer then they should rectify it. Smiley Happy

0 Kudos
Message 4 of 11
(177 Views)

This has nothing to do with LabVIEW not being able to run python code in parallel.  It has to do with your scope not being able to handle parallel queries.   While you don't explain what model scope you are using, I've never EVER, seen a scope that could accept commands from two programs at once.  Let alone handle things in parallel. 

 

The scope is simply accepting the query from which ever python script LabVIEW code executes first, finishing that query and then moving onto the next python script and its query.  Wouldn't matter what langue you use it will not work!

 

If you want your scope to do two things at once you better get out the manual and see if its possible to even do what you want.  If you explain what you actually want to accomplish, and with what equipment, then perhaps we can help you create an appropriate solution.  

 

Craig

0 Kudos
Message 5 of 11
(159 Views)
Solution
Accepted by topic author iamyevesky
06-17-2019 09:29 AM

Have you attempted to put the Python node in the while loop as demonstrated in the KB you linked?  I find the specific callout of text at the bottom interesting:  In the example above, you can see that While Loop is immediately terminated. But this construction will tell to the compiler that these two parts of code should run in parallel.

 

We use this approach, albeit unintentionally, where we call the VI that executes the Python code via the Start Asynchronous Call node and return its results via a queue created just prior to execution.  Our python calls some linear solvers that can take up to 5 minutes to execute, so if there is a change in state of our system that warrants restarting the optimization we can just launch another asynchronous call and destroy the first queue so the results do not get intermingled instead of waiting for the original optimization to complete before starting the new one.

 

Long story short, you can call the Python nodes in parallel, but it requires some finagling.  Try the while loop approach.  If that doesn't work, try using the Asynchronous Call.

 

async_python_call.png

 

Message 6 of 11
(152 Views)

@cstorey wrote:

This has nothing to do with LabVIEW not being able to run python code in parallel.  It has to do with your scope not being able to handle parallel queries.   While you don't explain what model scope you are using, I've never EVER, seen a scope that could accept commands from two programs at once.  Let alone handle things in parallel. 

 

The scope is simply accepting the query from which ever python script LabVIEW code executes first, finishing that query and then moving onto the next python script and its query.  Wouldn't matter what langue you use it will not work!

 

If you want your scope to do two things at once you better get out the manual and see if its possible to even do what you want.  If you explain what you actually want to accomplish, and with what equipment, then perhaps we can help you create an appropriate solution.  

 

Craig



Well that would have been a good excuse to my project manager but sadly that is not the case.

 

The temperature-measuring instrument device is different from the oscilloscope and do not on each other at all. Both processes run independent of each other, and do not use the same variables.

 

Though they wouldn't complete at the same time, they should be able to begin execution at the same time.

 

Here is the Python code underneath the node to give much more clarity

 

import time
import pyvisa
import serial

"""
:brief Converts list data from scope to voltage values 
:param list data type raw_data contains the values recorded
:from scope in ASCII format
:param  resourceManager object scope: access point of the oscilloscope
:return list data type voltage: recorded voltage values
"""
def convert_raw_voltage_data(raw_data, scope):
    #Query returns the y-axis scale on oscilloscope graph
    ymult = float(scope.query('WFMOutpre:YMULT?'))
    #Query returns the y-zero value of the oscilloscope graph
    yzero = float(scope.query('WFMOutpre:YZERO?'))
    #Query returns the offset value for the y-axis on the oscilloscope graph
    yoff = float(scope.query('WFMOutpre:YOFF?'))
    npoint=len(raw_data)
    voltage=['?']*npoint
    #Convert raw data values and fill into voltage list
    #Non-recorded data appears as '?' in voltage list
    for i in range(npoint):
        voltage[i]=ymult*(raw_data[i]-yoff)+yzero
    return voltage
"""
:brief sets the channel of the scope
:param  resourceManager object scope: access point of the oscilloscope
:param int data type channel: channel scope is to be set
"""
def set_channel(scope, channel):
    scope.write(":DATa:SOUrce CH"+str(channel))
"""
:brief obtains raw data from oscilloscope and converts it into voltage list
:param  resourceManager object scope: access point of the oscilloscope
:return list data type voltage: recorded voltage values
"""
def get_data(scope):
    scope.write("WFMOutpre:ENCdg ASCii")
    data = scope.query_ascii_values("CURVE?")
    voltage=convert_raw_voltage_data(data, scope)
    return voltage

"""
:brief prints recorded data into datasheet document with corresponding
 voltage and time recorded from each of the channels
:param  resourceManager object scope: access point of the oscilloscope
:param int data type numchan is number of channels
:param list data type voltage contains recorded voltage values
:param list data type times contains recorded time values corresponding
:with recorded voltage
:param float data type dt records the sampling time interval
:param file data type outfile: datasheet into which data would be stored 
:param stime data type String:Locale’s appropriate date and time representation
 param float data type timeins: time as a floating point number expressed in seconds since the epoch, in UTC.
""" 
"""
:brief obtains the times for which sampling data was recorded
:param  resourceManager object scope: access point of the oscilloscope
:param int data type record_length: number of sample data recorded
:return list data type times: the times at which sampling data was recorded
""" 
def get_time(scope, record_length):
    times=['?']*record_length
    xincr = float(scope.query('WFMOutpre:XINCR?'))
    print('xincr= '+str(xincr))
    for i in range(record_length):
        times[i]=float(i)*xincr
    return times  

"""
:brief obtains the recording time interval
:param  resourceManager object scope: access point of the oscilloscope
:return float data type xincr: the sampling time interval
""" 
def get_dt(scope):
    xincr = float(scope.query('WFMOutpre:XINCR?'))
    return xincr
"""
:brief obtains the total time for which data was recorded from oscilloscope
:param  resourceManager object scope: access point of the oscilloscope
:return float data type wait_time: total time taken for data sample collection
"""
def calc_wait_time(scope):
    record_length=float(scope.query(':HORIZONTAL:RECORDLENGTH?'))
    dt=get_dt(scope)
    wait_time=record_length*dt
    return wait_time

"""
:brief sets the scale for the graph of the data
:param  resourceManager object scope: the access point of the oscilloscope
:param int data type numchan: number of channels 
:param int data type short_record_length: unexplained
:param int data type long_record_length: unexplained
"""
def checkscale(scope, numchan, short_record_length, long_record_length):
    base_record_length=int(scope.query(':HORIZONTAL:RECORDLENGTH?')) 
    if short_record_length != base_record_length:
        scope.write(':Horizontal:Recordlength '+str(short_record_length)) #Why?
    hscale = 0.000000001*short_record_length*0.1 #Why?
    scope.write(":Horizontal:Scale "+str(hscale))
    #Sets the "from" part of the waveform to be captured. In this case from data point 1
    scope.write('DATA:START 1')
    #Sets the "to" part of the waveform to be captured. In this case to the last recorded data point
    scope.write('DATA:STOP '+str(short_record_length))
    scope.write('ACQUIRE:STOPAFTER RUnsTOP')
    for i in range(numchan):
        okayscale = 0
        while okayscale == 0:
            set_channel(scope, i+1)
            scope.write("WFMOutpre:ENCdg ASCii")
            data = scope.query_ascii_values("CURVE?")
            highcount = 0
            lowcount = 0
            for j in range(short_record_length):
                if data[j] > 32760: #Why 32760?
                    highcount += 1
                elif data[j] < 32760/5:
                    lowcount += 1
            if highcount/short_record_length > 0.0001:
                chscale = float(scope.query("CH"+str(i+1)+":SCALE?"))
                """
                Oscilloscope has scales of 1mV, 2mV, 5mV, 0.1V, 0.2V, 0.5V up
                to 10V in a pattern of 1,2,5.
                To increase scale when it is a unit value of 1 or 5, multiply by 2
                else multiply by 2.5.
                If scale is at 10V, then that is the max.
                """
                if chscale in [0.001, 0.01, 0.1, 1.0, 0.005, 0.05, 0.5, 5.0]:
                    newchscale = chscale*2
                if chscale in [0.002, 0.02, 0.2, 2.0]:
                    newchscale = chscale*2.5
                if chscale == 10:
                    okayscale = 1
                    print('CH'+str(i+1)+': already at max scale')
                    break                    
                scope.write("CH"+str(i+1)+":SCALE "+str(newchscale))
                print('CH'+str(i+1)+': changing scale from '+str(chscale)+' to '+str(newchscale))
            if lowcount/short_record_length > 0.9999 and highcount/short_record_length < 0.0001:
                chscale = float(scope.query("CH"+str(i+1)+":SCALE?"))
                if chscale in [0.01, 0.1, 1.0, 10.0, 0.002, 0.02, 0.2, 2.0]:
                    newchscale = chscale/2
                if chscale in [ 0.005, 0.05, 0.5, 5.0]:
                    newchscale = chscale/2.5
                if chscale == 0.001:
                    okayscale = 1
                    print('CH'+str(i+1)+': already at min scale')
                    break                     
                scope.write("CH"+str(i+1)+":SCALE "+str(newchscale))
                print('CH'+str(i+1)+': changing scale from '+str(chscale)+' to '+str(newchscale))
            if highcount/short_record_length < 0.0001 and lowcount/short_record_length < 0.9999:
                okayscale = 1
        scope.write(":Horizontal:recordlength "+str(long_record_length))
        hscale = 0.000000001*long_record_length*0.1
        scope.write(":Horizontal:Scale "+str(hscale))

"""
:brief reads data from oscilloscope and returns recorded voltage
:param resourceManager object scope: the access point of the oscilloscope 
:param int data type numchan: number of channels 
:param int data type nstart: unexplained
:param String data type descriptor: describes the experiment undertaken
:param int data type short_record_length: unexplained
:param int data type long_record_length: unexplained
"""
def read_and_write_data_from_Nch(scope, numchan, nstart, short_record_length, long_record_length):
    checkscale(scope, numchan, short_record_length, long_record_length)    
    wait_time = calc_wait_time(scope)
    record_length=int(scope.query(':HORIZONTAL:RECORDLENGTH?'))
    scope.write('DATA:START 1')
    scope.write('DATA:STOP '+str(record_length))
    scope.write('acquire:stopafter sequence')
    voltage = [['?']*(record_length)]*numchan
    for i in range(numchan):
        set_channel(scope, i+1)
        voltage[i]=get_data(scope)                
        time.sleep(wait_time)
    set_channel(scope, 1)
    times=get_time(scope, record_length)
    scope.write('FPAnel:PRESS runstop')
    
    output = [times]
    for i in range(numchan):
        output+=[voltage[i]]
    scope.close()
    return output

"""
:brief plots the data received from oscilloscope
:param resourceManager object scope: the access point of the oscilloscope 
:param int data type nstart: unexplained
:param int data type i: channel from which data is read
:param list data type times: the times at which sampling data was recorded
:param list data type voltage: recorded voltage values
:param String data type figoutfile: adress where graphs plotted are stored
:param String data type descriptor: describes the experiment undertaken
:return
"""
"""
:brief
:param
:param
:return
"""
def mainforAmbrell(numchan, nstart, j, short_record_length, long_record_length):
    
    #Initialize system for data-taking
    rm = pyvisa.highlevel.ResourceManager()
    #Visa address for Tektronik Osciloscope
    visa_address = 'TCPIP::169.254.3.117::INSTR'

    scope = rm.open_resource(visa_address)
    #Open channels
    scope.write(":SELECT:CH1 on")
    scope.write(":SELECT:CH2 on")
    scope.write(":SELECT:CH3 on")
    #Encodes oscilloscope data into ASCII format
    scope.write(":DATA:ENCDG ASCII")
    
    scope.write('Data:width 2')
    
    return read_and_write_data_from_Nch(scope, numchan, nstart, short_record_length, long_record_length)

def scopeRead(shortRecordLength, longRecordLength, numCollects, numChan):
    short_record_length = shortRecordLength
    long_record_length = longRecordLength
    #descriptor = 'CH1-Hcoil-CH2-Mcoil-empty-test'
    numcollects = numCollects
    numchan= numChan
    
    """
    note time between data collections is about 16 seconds already because of various oscilloscope data transfer wait times
    (which are probably overly conservative)
    so any nonzero pause value makes that wait time longer than 16 seconds
    """
    pause = 0
    nstart = 0
    output = []
    
    for j in range(numcollects):
        output += [mainforAmbrell(numchan, nstart, j, short_record_length, long_record_length)]
        time.sleep(pause)
    
    return output

def opsensRead(numtimes):
    delay = 0.02;
    ntimes = numtimes;

    ser = serial.Serial("COM3",9600, timeout=((ntimes*delay)+2))
    unicodestring = "measure:start "+str(ntimes)+"\n"

    ser.write(unicodestring.encode("ascii"))
    rawData0 = ser.read(ntimes*10)
    rawData0 = rawData0.decode("ascii")
    data0 = rawData0.split('\n')

    removeMisc = ['\4','CH1','']
    errors = ['Err -170','Err -201', 'Err -140','Err -160']
    data1 = []

    for y in range(len(removeMisc)):
        while removeMisc[y] in data0:
            data0.remove(removeMisc[y])
    
    for x in range(len(data0)):
        time = x*float(delay)
        temp = "?"
        if data0[x] not in errors:
            temp=float(data0[x])
        data1.append([time,temp])
    ser.close()
    return data1
0 Kudos
Message 7 of 11
(150 Views)

Briefly looking at that Python code, it raises some flags that you're attempting to communicate to the same device through Serial in a parallel fashion.  Even if you get parallel execution working, you're going to have race conditions where you get into a situation where multiple commands/queries are sent to the device and the wrong instance of your python code reads a response to a query sent the device from a different instance.  Just something to take into account

 

Upon re-reading your description of your system setup, I see that you're actually communicating with two different devices.  Whoopsie

0 Kudos
Message 8 of 11
(143 Views)

TL;DR the python code.  What happens in you just use a batch scripts and call both simultaneously?

 

Again, maybe some explanation of what you are trying to do, how things are connected, etc.. would help get to a solution.  Do both the scripts use the same bus?  (i.e. are they fighting for resources of GPIB/RS232 at teh same time?)  How are you physically connected to both?  Why not just do both calls in python if you are only using LabVIEW for a GUI?

 

 

0 Kudos
Message 9 of 11
(140 Views)

@DHerron wrote:

Have you attempted to put the Python node in the while loop as demonstrated in the KB you linked?  I find the specific callout of text at the bottom interesting:  In the example above, you can see that While Loop is immediately terminated. But this construction will tell to the compiler that these two parts of code should run in parallel.

 

We use this approach, albeit unintentionally, where we call the VI that executes the Python code via the Start Asynchronous Call node and return its results via a queue created just prior to execution.  Our python calls some linear solvers that can take up to 5 minutes to execute, so if there is a change in state of our system that warrants restarting the optimization we can just launch another asynchronous call and destroy the first queue so the results do not get intermingled instead of waiting for the original optimization to complete before starting the new one.

 

Long story short, you can call the Python nodes in parallel, but it requires some finagling.  Try the while loop approach.  If that doesn't work, try using the Asynchronous Call.

 

async_python_call.png

 


Worked!

 

Answer was right in front of me the whole time!

0 Kudos
Message 10 of 11
(124 Views)