LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

Structuring a large application with classes, FGV's etc.

Hi all,

 

I'm looking for some advice/input on how to structure large application with multiple hardware interactions and data acquisition/processing in a nice way, so that parts are easily maintainable and reusable. I've managed to create a working version. Now I need to create an almost identical copy with different hardware, and it is clear that my application is built like Frankenstein's monster.

 

The application uses multiple hardware devices (visa serial, daqmx and imaq). The basic structure is added in the snippet: it has an initialization step, followed by parallel data acquisition (producer), data processing "state machine" (producer & consumer) and data logging (consumer). Furthermore there are two parallel loops for front panel actions and receiving external triggers (interrupts for the state machine).

I decided for the double producer/consumer so that these steps would not jeopardize the timing of the system.

 

Since multiple parallel loops require information about connections, output data etc., I created one massive Master-cluster (cluster of clusters) to pass on all the data without having too many wires. Currently this contains somewhat 7 sub-clusters and is becoming a monster itself. (**Note: data transfer between loops is via queues.)

The subVI's take this Master cluster as input and extract the required data from it. To me it feels like a massive entanglement of subVI's and clusters, and changing one thing leads to a string of conflicts that need to be updated.

 

Basically I want to tame this dragon before someone else has to work with it. Of course I'm looking into classes like I'm used to in Python and C++ programming, but I have to practice a bit with these in LabVIEW still.

Additionally I found that Functional Global Variables (fgv's) are a great way to pass data between subVI's (looking at this example at least). I think that would severely improve readability and modularity of my application. I think I would need to stick with the Producer/Consumer-layout, but it would be a lot nicer with more modular subVI's.

 

How would I implement this? Classes for all the hardware devices, subVI's with FGV's for all the data acquisition, processing, and logging? One subVI for example that contains the entire state machine, so that my main VI is super clean?

Any other methods that I'm overlooking?

 

Happy to hear your thoughts on this.

0 Kudos
Message 1 of 16
(3,854 Views)

There are so many options. But I have some suggestions to start with.

 

Check https://forums.ni.com/t5/LabVIEW-Development-Best/Measurement-Abstraction-Plugin-Framework-with-Opti...

 

And take a look at the example "measure and log" in the example finder

Certified LabVIEW Architect
Message 2 of 16
(3,837 Views)

Thanks for the tips!

The Measure and Log example looks a lot like what I am trying to achieve. And shows that I really created Frankenstein's monster.

I will also look into the other link later.

0 Kudos
Message 3 of 16
(3,808 Views)

Whenever defining a system you should seriously consider what data is being passed to what components. If you are using the "Uber clsuer" to pass everything to everyone, that is a bad design. You should only be passing actual data that is required/necessary for the component to do its job. Good OOP design can help with this definiton but simply using classes does not guarantee a good design. I would recommend looking at the SOLID principles of system design.



Mark Yedinak
Certified LabVIEW Architect
LabVIEW Champion

"Does anyone know where the love of God goes when the waves turn the minutes to hours?"
Wreck of the Edmund Fitzgerald - Gordon Lightfoot
0 Kudos
Message 4 of 16
(3,783 Views)

When you are wondering if you should use a class or an FGV for something, you will want to think about the question: "Will I need more than one of these?" If you end up needing more than one, then the class will be a lot better as you can easily make multiple instances.

 

An even better question to ask when considering whether to use a FGV is "Would it be bad if I had more than one of this thing?" If the answer to that is yes, it really is something that your application should fundamentally only have one of, then a FGV is a really good solution because it is fundamentally global. But if it wouldn't be bad and would be foreseeably useful to have more than one, then you should probably make a class.

Message 5 of 16
(3,773 Views)

I may not be reading your OP correctly but if so it's worth mentioning the following:

Do FGV's or OOP.  Don't do both.FGVs have their downfalls and OOP has it's learning curve but using FGVs with OOP in LabVIEW is a great way to create a impossible-to-debug mess.  I've done both.  FGVs have a much lower learning curve, the challenges have already been mentioned above.  OOP is tougher but has a lot of benefits, especially when combined with a QMH of some kind.  This is my recommendation if you are comfortable with it.

 

http://www.ni.com/tutorial/53391/en/

 

0 Kudos
Message 6 of 16
(3,750 Views)

This all looks very interesting, thanks for the advice! I need some time to look into this a bit more.

 

The Measurement & Log example with the VI's seems like a very elegant solution. In principle it is similar to what I have now, but way neater organised.

 

"Whenever defining a system you should seriously consider what data is being passed to what components. If you are using the "Uber clsuer" to pass everything to everyone, that is a bad design. You should only be passing actual data that is required/necessary for the component to do its job."

 

This is in principle what I do. Data only gets written/modified in one place. But since I do not want to clutter the main VI with unpacking the cluster, I generally  make my subVI's take the "Uber Cluster" as input and do the unpacking there. I do avoid modification of the data in two spots.

0 Kudos
Message 7 of 16
(3,694 Views)

Disclaimer: My coffee has not kicked in yet.

 

I suggest you first step back and develop a design document that shows the components of the system you are trying to tame. The first doc should look a lot like the hardware/software elements that compose the system.

 

Then revise that diagram and assign meaningful names to the software components that you need to implement. That diagram should show what interactions take place between those components.

 

I offer the following image from a blog post I wrote (found here) below as a simple example.

 

Design.png

For each of the interactions that you define/discover you can THEN choose from the various synchronization or design patterns that LabVIEW offers.

 

In other words let the application requirements define the application and do not limit yourself to the simple out of the box solutions.

 

Now since the previous posts have compared OOP and FGV, let me please invite you to review that blog entry I linked above. I compares LVOOP with Action Engines in libraries and may help you benefit from the simple to understand Action Engine with the re-use available with Libraries.

 

Done for now.

 

Ben

 

Retired Senior Automation Systems Architect with Data Science Automation LabVIEW Champion Knight of NI and Prepper LinkedIn Profile YouTube Channel
0 Kudos
Message 8 of 16
(3,660 Views)

@irPaul wrote:

 

"Whenever defining a system you should seriously consider what data is being passed to what components. If you are using the "Uber clsuer" to pass everything to everyone, that is a bad design. You should only be passing actual data that is required/necessary for the component to do its job."

 

This is in principle what I do. Data only gets written/modified in one place. But since I do not want to clutter the main VI with unpacking the cluster, I generally  make my subVI's take the "Uber Cluster" as input and do the unpacking there. I do avoid modification of the data in two spots.


IF you are passing the Uber cluster than you are not following my advice. Just because you only modify a portion of it doesn't mean you are only passing the relevant data. You are still passing ALL of the data to everyone. I very rarely use "uber" clusters. Generally the only place they will exist is in a shift register in a state machine to minimize the wires running through the state machine. This "uber" cluster is usually comprises of many other well defined clusters with very specific purposes. Those are the pieces of data that get passed to subVI or other components of the system. Passing all data to everyone opens the doors for bugs that are very difficult to trace since the data can get changed anywhere in the system.



Mark Yedinak
Certified LabVIEW Architect
LabVIEW Champion

"Does anyone know where the love of God goes when the waves turn the minutes to hours?"
Wreck of the Edmund Fitzgerald - Gordon Lightfoot
Message 9 of 16
(3,639 Views)

You are at a very interesting stage in your LV career! You are starting to think of applications in terms of architecture and not just in terms of code. You are thinking about how data flows around the submodules and you are trying to use familiar constructs in inappropriate ways. It is great that you are feeling uncomfortable and seeking guidance at this stage. So many people plow on regardless and then have a hard time getting out of bad habits.

 

The things we will discuss are true for all programming languages, so this experience will help you be a better software engineer not just a better programmer.

 

Consider doing the Advanced Architectures course. When you get to grips with these concepts then consider going for the CLA.

 

Mark Yedinak is right, ubercluster bad! This is a familiar solution used in an inappropriate place. Transfer only what you need. Each subloop (module) has a set of messages it expects to receive in its queue. The messages consist of instruction (read channel) and data (channel#, num samples). Think about modules in terms of the messages they receive and try to define this interface before you start coding!

 

I don't like FGVs here, they are really useful if they are functional, but you are describing a global variable, not a functional global variable. If you are using them to pass data, use a global or a queue depending on if you are ok with your data being lossy: Globals only pass the current data, queues ensure the receiver sees every piece of data.

 

You are quite right that there is a cleaner way to do sub modules. I wrote a really cut down version of QMH that might help here. See attached. The master just contains the vi's of the submodules. Submodules communicate via events. In this case, every module will receive every message sent by every module, but each submodule gets to decide whether it responds. Submodules have their own data cluster that contains all the data pertinent to that cluster. Libraries are used to define submodules. If you want to make a new submodule, duplicate the template library. Then put the new main.vi in the master main and wire up as appropriate. Please ask me any questions!

 

 

CLA - Kudos is how we show our appreciation for comments that helped us!
0 Kudos
Message 10 of 16
(3,596 Views)