LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

LVOOP HAL Commonality Best Practice

Hi All,

 

Need some advice when it comes to HAL best practices.  I know the idea is to create parent methods with enough commonality so that different children/HW can inherit and use it.  However, I am starting to find myself running into situations where I am not sure is the best design route to take as different brands or models will have slightly different implementation or capabilities.

 

For example: I currently have a "DC Power Supply Parent" with 4 Children, NI-DC Power Supply, an Agilent, a Keithley, and a gWinstek. My parent implementation of "Enable Output" is just a T/F bool to determine whether to turn on the output regardless of the number of channels.  Between these 4 brands, they have 2 ways of implementing "Enable Output".  The Agilent and gWinstek's "Enable Output" function defined by their driver is simply a boolean T/F and enable all output channels while the NI and Keithley only turn on outputs by channel number.  This is making me revisit my parent "Enable Output" and wondering if anything should change due to some instruments requiring extra inputs.  Should I just keep the implementation as is and code the NI and Keithley to turn on all channels? Or should I change the parent to include the channel numbers, but the Agilent and gWinstek will do nothing with the channel number input and just keep on enabling all? 

 

Any perspectives on this?  I have a feeling it boils down to just seeing what the overall most common use-cases are going to be for these supplies and code around that...

 

Regards,

Mike

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

Mike,

 

there are multiple ways how you can solve such requirements. All have their advantages and disadvantages.

Sadly, one option other OOP languages can provide is not possible in LV OOP: Overloading.

 

 

Besides that, you can do the following in LV OOP:

1. Create multiple versions of the same functionality with unique name, e.g. "Enable Output All", "Enable Output Channel". This might be the least desirable option.

2. For all functions having arbitrary parameter sets, use a variant input. This is rather simple in your use-case as we are talking about inputs. When talking about outputs, it nihilates most of the advantage of using OOP. The idea (for inputs) is that each child implements an appropriate variant-to-data. The disadvantage is that (a) you cannot right click the input to create a suitable constant and (b) that there is no implicit typecheck during development which can lead to runtime errors (using incorrect parameter setup for used plugin class)

3. Use a parameter class which abstracts the parameter interface. However, this increases complexity of the code by a lot.

 

Defining constraints is similar to the approach which was chosen for IVI (interchangeable virtual instruments). IVI defines a minimum set of functions/parameters for instrument classes and limits their usage to that definition. As a result, if a developer wants (needs) to use instrument functionality which is not covered in IVI, he has to use the instrument specific API.

Norbert
----------------------------------------------------------------------------------------------------
CEO: What exactly is stopping us from doing this?
Expert: Geometry
Marketing Manager: Just ignore it.
Message 2 of 12
(4,292 Views)

Thanks for the feedback Norbert...you are right, having the ability to overload would really be ideal here.

 

I am curious about:


3. Use a parameter class which abstracts the parameter interface. However, this increases complexity of the code by a lot.


Can you give an example on how this works?  I can't seem to wrap my head around it.  I mean, conceptually yes, I think having another parameter class that can dynamically dispatch and filter down to the correct child makes sense, but can't seem to picture it coming together.  Also, would you make a parameter class per instrument type parent class (DMM, SCOPE, DC PSU, etc) or would you form one for each particular shared child method that requires overloading (or differently typed inputs)?

 

Regards,

Mike

 

Best Regards,

Mike

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

Whoops, sorry about that confusion, apparently I was mixing up my personal account and corporate account log-ins.

-Mike

0 Kudos
Message 4 of 12
(4,263 Views)

This shows the general context.

Norbert
----------------------------------------------------------------------------------------------------
CEO: What exactly is stopping us from doing this?
Expert: Geometry
Marketing Manager: Just ignore it.
0 Kudos
Message 5 of 12
(4,255 Views)

Like Norbert says, whenever I have felt like using a varient input in LVOOP I have ended up scraping it and putting it down to not understanding my class breakdowns well enough. I went back to the drawing board and redrew my classes and am a lot happier with the outcome.

 

I remember listening to a general OOP podcast (cant remember which I am afraid) where they talked about how classes can be really small. They can include just a single function or property. More is better.

 

I have a similar situation to OP where I have different types of kit made by different manufacturers who use different protocols; my NI kit all talk through ethernet and some of the other kit all talks using this weird by reference handle thing. Within their types, instruments might do very similar functions, and similar all the kit of a single manufacturer might use the same protocols.From a code reuse perspective, which should the earliest common ancestor be? The instrument or the manufactuer? If I do it as the manufacturer then can reuse the protocol code but I have to redevelop some instrument code. And vice versa. There doesn't seem to be an easy way I can win on all counts. In the end I decided to go with the Instrument being earliest common ancestor then branching out to manfacturer but this was because my application required that instrument type is more important than manufacturer; on my GUI I group items by type, not manfacturer.

CLA - Kudos is how we show our appreciation for comments that helped us!
0 Kudos
Message 6 of 12
(4,239 Views)

I can tell you what I did in a very similar situation.

 

My "enable" VI also has a channel input.  Not required, defaults to the lowest channel or the only channel on the supply.

 

On a single-channel supply, or a supply that allows enabling of one channel without enabling the other, nothing special is required.

 

On a multi-channel supply that enables all channels or no channels, I had to use a different strategy. 

 

First, I set up a local cache to store instrument settings for that particular instrument (indexed by the communications address, so if the wire forks or there are multiple supplies the stored settings aren't ever lost or mixed up).  The settings it stored were the max volts, max amps, and on/off status for each channel separately.  

 

Second, each time a VI tried to turn a channel on or off or change its volt/amp settings, it would look up those cached settings, and do some logic.

1. Are all channels off, and this is a volt/amp change?  If so, just cache the change.

2. Is this is a volt/amp change to a channel currently on?  Send the change, and cache it too.

3. Is this turning a channel off while at least one channel needs to remain on? Set the volts and amps on the supply to zero for that channel, keeping the volt and amp settings cached the same while also caching that channel as being "off".

4. Is this turning on a channel when a different channel was currently on?  All you need to do is set the volts/amps for the new channel to their cached values.

5. Is this turning off a channel, and it's the last one to be turned off?  Send the "off" command to the supply but also set the volts and amps to zero on the supply, while not touching the cached values.

 

There might be a situation or two that I didn't describe, but I think that should give you the idea.

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

@MaxJoseph wrote:

I have a similar situation to OP where I have different types of kit made by different manufacturers who use different protocols; my NI kit all talk through ethernet and some of the other kit all talks using this weird by reference handle thing. Within their types, instruments might do very similar functions, and similar all the kit of a single manufacturer might use the same protocols.From a code reuse perspective, which should the earliest common ancestor be? The instrument or the manufactuer? If I do it as the manufacturer then can reuse the protocol code but I have to redevelop some instrument code. And vice versa. There doesn't seem to be an easy way I can win on all counts. In the end I decided to go with the Instrument being earliest common ancestor then branching out to manfacturer but this was because my application required that instrument type is more important than manufacturer; on my GUI I group items by type, not manfacturer.


One way around this is to abstract the connection itself, i.e., the Instrument (abstract class) has a Connection object in its private data. The Connection object is itself another abstract class, which has concrete children of Serial connection, or TCP connection, or USB reference connection, etc. Each subclass will write the correct Connection object to its abstract parent. You have some weirdness when you initialize, as each Initialize function needs its own separate inputs, but after that it's smooth sailing.

 

If your Agilent equipment uses much of the same protocols, have an "Agilent Connection" concrete class, which can be used by a number of different Agilent devices.

0 Kudos
Message 8 of 12
(4,233 Views)

One way around this is to abstract the connection itself, i.e., the Instrument (abstract class) has a Connection object in its private data. 


This was the route I took on a somewhat similar project.  A while later I learned that the connection bus/hardware/driver "type" (TCP, Serial, .NET ref, etc.) didn't make for the best abstraction after all.  There was another classification, probably a more fundamental one, related to syntax/protocol.  

 

Knowing the connection type only *partly* narrowed down the possible protocols.  On the other hand, knowing the protocol would also only *partly* narrow down the possible connection types.  There wasn't a clean abstraction using just one or the other.

 

Summing up, I endorse the general idea of abstracting the connection and/or protocol.  But I also want to caution you that doing it *well* may not be trivial.

 

 

-Kevin P

 

P.S.  As a point of reference, VISA is an example of abstracting based on *protocol* or syntax, as it's (largely) agnostic to the underlying bus/hardware connection.

 

 

 

 

ALERT! LabVIEW's subscription-only policy came to an end (finally!). Unfortunately, pricing favors the captured and committed over new adopters -- so tread carefully.
0 Kudos
Message 9 of 12
(4,222 Views)

I think this picture from Norbert's link perfectly illustrates my issue (except it is dealing with outputs, I am dealing with inputs...but probably will run into this problem sooner or later):

 

output.png

My problem here is I don't see how I am getting away from my problem of conflicting I/Os with another layer of OOP abstraction.  I end up getting another dynamic dispatch parent class connector conflict yet again.

 

-Mike

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