LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

LVOOP HAL- Interface Class Implementation

I am relatively new to LVOOP and developing a HAL. A basic class diagram can be seen in the picture. Class.jpg

The DMM class is the Interface class. NiDMM is the Instrument-specific plug-in class. The NiDMM class has, in addition to the open connection method, a open connection with options method. The open connection method in the Base device class will be overridden by NiDMM by dynamic dispatch. 

Correct me if I am wrong. My understanding is that I program with the Interface(DMM) class and any predefined plug-in class with DD methods will be called in its place at run time. Now that I have a new device specific method (open conn with options), do I have to create a DD method in the DMM Class?

And If I have a new plug in class with a device specific method not defined in parent classes, I have to again define a memeber VI stub in the interface class? 

0 Kudos
Message 1 of 12
(4,815 Views)

And this is where HALs go crazy.

 

My advice is to limit your interface as much as possible.  But that gets really hard at initialization.  So what I have been doing is using a configuration file that you can use to set all of the special variables that are used at initialization (class to actually load, resource name, VISA resource, etc).  I have been starting to transition to XML; others recommend JSON.  This keeps my initialization interface from getting way out of whack (just need the configuration file name or reference).


GCentral
There are only two ways to tell somebody thanks: Kudos and Marked Solutions
Unofficial Forum Rules and Guidelines
"Not that we are sufficient in ourselves to claim anything as coming from us, but our sufficiency is from God" - 2 Corinthians 3:5
0 Kudos
Message 2 of 12
(4,789 Views)

Yes, but in general, you shouldn't let your children drive the design of your abstraction, otherwise, every time you need a new method in a child, you will have to add it to the abstraction, which is indicative of a design flaw. 

 

What is special about the 'connect with options' method? Why would you want to call that versus the regular connect? Can you put it inside your connect method, with the selection on which connection method to run being called inside the connect method?

 

What I am getting at here is that if your child has multiple ways of implementing an abstract method, the child itself should determine which to use, and the abstraction shouldn't care about those details. 

0 Kudos
Message 3 of 12
(4,767 Views)

Short answer: Yes.

 

Longer answer: I had a somewhat similar project of putting together a HAL using LVOOP when I was pretty new to both as well.   I got it as far as a decent proof of concept prototype, but had moved on to another job before the project went to completion.  So I can't really say *exactly* how well the design worked out.

 

Anyway, one thing I did that *seemed* help was an approach I later learned to be a known design pattern called "Composition".   The base instrument class (generic "DMM" for you) contained a base class communication object as one of its data members.   (This "has a" relationship is what defines the composition pattern.)

 

In my situation, various proprietary devices offered largely overlapping functionality but several different communication connection types.   The communication class supported basic common functionality like Open(), Close(), Read(), Write(), Status().   The way I broke it down was that the instrument class was responsible for knowing differences in syntax while the communication class was responsible for differences in connections and protocols.  

 

The way it might work is that for an instrument class function like "Measure Volts DC()", the instrument object would override that function and form a communication string with the correct syntax for that specific instrument.  It would then send the string to the Read() function of the communication object it contains.  The communication object that was actually instantiated would have overridden the generic Read() function, and would enact that message over its connection protocol.  (For example, it may add a line terminator for serial, pad to a fixed packet size for TCP/IP, translate into a special dll API function to call for a proprietary USB connection, etc.)

 

This separation helped prevent an explosion of instrument class functions to implement.

 

I worked out this design before I had a lot of familiarity with the devices in question.  Late in my time there, I came to believe that there might have been a better design where the communication object hierarchy was based more on the syntax of the messages than on the hardware connection of the comm link.  However the decision between these approaches would have been very much based on the particularities of the devices being supported and the ways they could be categorized as groups and individuals.

 

In short, I think a good HAL design first requires a thorough understanding of the functionality that's being abstracted.  But it may also be pretty important to understand the implementation details over the range of devices being supported.  These will help you to make good choices in defining categories that lead to a class hierarchy.

 

Having this one limited experience designing and implementing a HAL, I'd say to plan for a lot of upfront design work, a fair bit of overhead in the beginning stages of implementing abstract base functions and accessors, and finally some late-breaking magic when the upfront LVOOP work starts to pay off.  The upfront cost to downstream benefit ratio is probably not always easy to predict.  There are times it'll pay off well, times when it's a toss-up, and times it won't be worth it.  I've got no general insight for guessing where you might land.

 

 

-Kevin P

 

CAUTION! New LabVIEW adopters -- it's too late for me, but you *can* save yourself. The new subscription policy for LabVIEW puts NI's hand in your wallet for the rest of your working life. Are you sure you're *that* dedicated to LabVIEW? (Summary of my reasons in this post, part of a voluminous thread of mostly complaints starting here).
Message 4 of 12
(4,758 Views)

Connect with options sets the initial state of some session properties. But the question was mainly to see how the open connection abstract method can use it. You're saying that the child class(for instance, with both methods) should determine which one to use. In that case, would I go for a enum (same connector pane) to choose the VI in the abstract VI?

 

Also I have a NiDMM plug-in class with a lot of methods that must be abstracted. I realize that it's a bad design to implement every VI as abstract but I don't know how to proceed.

NiDMM1.JPG

 

NiDMM2.JPG

 

 

0 Kudos
Message 5 of 12
(4,738 Views)

If you use the enum in the connector pane of the abstraction, your child is still driving the design of your parent. Will you need to update that enum when another child also has multiple ways of opening a connection?

 

The way I have addressed your problem is by calling a 'setup' and then an 'action'. Your setup method will have a generic input - you could make it a variant, or strings - call one a 'setup mode' and the other 'setup data', or even a configuration object class if you wanted. (I worked on a very large and powerful HAL framework that all configuration was passed in as strings, and it worked well - the times I have used config objects it was usually more trouble than worth). Setup mode would contain the data informing your driver what it is setting up for, and the setup data would be the data needed for that particular setup.

 

So a more concrete example, it looks like calibration will be a part of your HAL - so in the abstract parent, define a 'setup for calibration' and a 'run calibration step'. In your setup for calibration method, the DMM class overrides and parses the input to figure out what it will be doing next - one of the inputs should tell it what type of calibration it will run. It saves that configuration to class data, then in the run calibration step you pull that out of class data, and select the proper method to run based on that. 

0 Kudos
Message 6 of 12
(4,735 Views)

Hey Paul,

thanks for the reply. I have tried something but it’s not a complete solution.

So right now, the interface VI’s are totally empty except for the class and error terminals. The child class VI’s consist of a case structure with an enum selector to choose the method to run. This enum(for instance, to set the configuration settings) is defined in one of the steps previous to interface configuration step in Teststand. The enum(setup mode) for case selection and the associated settings (setup data) are in the class private data. To make it easier for the Teststand developers, I chose enum over strings for setup mode.

The problems I’m facing right now are

1. Is this approach a proper way 

2. If there is a need to configure or use a Utiliy of the instrument twice within a single VI, I have no way of resetting the enum value for different functions

 

 

0 Kudos
Message 7 of 12
(4,684 Views)

@GoKu25 wrote:  The child class VI’s consist of a case structure with an enum selector to choose the method to run.

This seems completely wrong to me.  Since you are also messing around with TestStand, let me tell you what I am currently doing.

 

First of all, I have a main class for each type of instrument in my rack (DMM, Switch/Relay/Mux, RF Generator, Power Supply, etc).  That class belongs inside a library.  This library has, in addition to the parent class, has all of the accessors that TestStand will use.  Think of these accessors as wrappers around the class.  So this way I keep a reference(s) to the instrument(s) of that type in a Global Variable or Action Engine that the accessors use to get the object for the instrument I want and then use that to call the appropriate method(s) of the class.  I then just let Dynamic Dispatch handle the fine details based on which class the object really is (loaded dynamically).  Notice I am not using an enum to state which method to call.  I have been down that route before and it ends up leading to confusion due to all of the extra inputs that you don't need for a particular method.  Instead, I just have a method to do one particular thing and the TestStand developer can just use that.

 

If you want to go further down this rabbit hold, I then build that library into a PPL.  I then use that PPL in my inheritance tree for any child classes I need to make.  Those children also get built into PPLs and I now have a nice plugin architecture.


GCentral
There are only two ways to tell somebody thanks: Kudos and Marked Solutions
Unofficial Forum Rules and Guidelines
"Not that we are sufficient in ourselves to claim anything as coming from us, but our sufficiency is from God" - 2 Corinthians 3:5
0 Kudos
Message 8 of 12
(4,666 Views)

@crossrulz wrote:

@GoKu25 wrote:  The child class VI’s consist of a case structure with an enum selector to choose the method to run.

This seems completely wrong to me.  Since you are also messing around with TestStand, let me tell you what I am currently doing.

 

First of all, I have a main class for each type of instrument in my rack (DMM, Switch/Relay/Mux, RF Generator, Power Supply, etc).  That class belongs inside a library.  This library has, in addition to the parent class, has all of the accessors that TestStand will use.  Think of these accessors as wrappers around the class.  So this way I keep a reference(s) to the instrument(s) of that type in a Global Variable or Action Engine that the accessors use to get the object for the instrument I want and then use that to call the appropriate method(s) of the class.  I then just let Dynamic Dispatch handle the fine details based on which class the object really is (loaded dynamically).  Notice I am not using an enum to state which method to call.  I have been down that route before and it ends up leading to confusion due to all of the extra inputs that you don't need for a particular method.  Instead, I just have a method to do one particular thing and the TestStand developer can just use that.

 


The issue he is trying to address is how to do custom measurements with a generic interface. If your child instrument has some custom action you dont want to define at the parent level, how do you call it from the parent level? My suggestion was to use two methods defined at the parent level - configure for measurement, and take measurement. The configure takes in some data that the child knows how to process, including some information about which measurement method to call if needed. I dont see anything wrong with that design, but Im open to being convinced otherwise!

 

 

Message 9 of 12
(4,653 Views)

@GoKu25 wrote:

Hey Paul,

thanks for the reply. I have tried something but it’s not a complete solution.

So right now, the interface VI’s are totally empty except for the class and error terminals. The child class VI’s consist of a case structure with an enum selector to choose the method to run. This enum(for instance, to set the configuration settings) is defined in one of the steps previous to interface configuration step in Teststand. The enum(setup mode) for case selection and the associated settings (setup data) are in the class private data. To make it easier for the Teststand developers, I chose enum over strings for setup mode.

The problems I’m facing right now are

1. Is this approach a proper way 

2. If there is a need to configure or use a Utiliy of the instrument twice within a single VI, I have no way of resetting the enum value for different functions

 

 


Item 1 - Hard to say if this is 'proper' as there isn't a clear line on what is proper - it all depends on how far you want to go with this - you could easily sink 1000s of hours into a HAL for teststand - it doesnt make sense to compare a framework of that size to something much smaller that obviously won't be as robust or powerful.  My concern based on what you have told me is - Who owns the enum? The parent or the child? In either case, it seems like you are tying the parent and child together in a way that you don't want to - if the child owns it, the parent is now dependent on the child. If the parent owns it, the parent will need to be updated for new child methods. Neither case is good. 

 

Are you using custom step types? 

 

For item, two, you could just define at the parent level a 'Perform multiple operations' with the configure taking in an array of enums or strings, and looping over the array, calling the configure and action inside each iteration.

0 Kudos
Message 10 of 12
(4,643 Views)