LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

Hardware abstraction using interfaces: How to do?

Hello, I've recently learned about interfaces and I think it's perfect for my purpose. I have a program to supply a voltage and measure the resulting voltage and current, this program is used by different acquisition devices in any combination. I have devices that only output (function generator), only input (oscilloscope) or do both (source and measurement unit). I've made the following class hierarchy based on this blog post: https://boringengineer.com/2020/05/03/labview-g-interfaces-solving-a-decade-old-problem/

Basjong53_0-1622466657329.png

 

The basic implementation should be that the supply and DMM will have an initialize device, then configure based on the user settings and then start. Once the measurement with the DMM has finished, they both stop and release the reference. Here the basic block diagram:

Interface Diagram.png

 

Now, I understand how to implement functionality if fine if you actually have two different devices, but let's look at the example when both the output and input is the SMU class; there are two init subVIs, but the device should only be initialized once. Furthermore, how can the 'measure' VI access the information set by the 'config signal' VI?

 

I have the idea of interfaces down, but the actual implementation still eludes me. Can someone help me in the right direction?

Message 1 of 7
(1,635 Views)

I would say that your "Init" method is not part of your interface but instead belongs with the concrete classes. The advantage of polymorphism (and, by extension, interfaces) is in limiting the amount of code that depends on concrete classes, instead depending only on contracts. But, for polymorphism to be actually usable, some part of an application's code needs to create actual concrete implementations and set up any custom configuration (also known as a "composition root"). In your case, this would be when the user settings are known (including which concrete classes are to be used) and the process has been initiated. 

 

With regards to your second question. Your issue with the "example code" is not related to polymorphism or interfaces. Don't forget that LabVIEW objects are by-value inherently, so the top "output" object wire data is not the same literal data as the lower "input" object wire. They started as copies but from thereafter they are independent.

 

In your case you may want to 'share' the SMU object and the data it contains between your parallel processes. That brings you to a reference-based design and then having to handle resource protection and ensuring atomic operation. But I would have to ask - did you actually want two parallel processes?

0 Kudos
Message 2 of 7
(1,556 Views)

@tyk007 wrote:

I would say that your "Init" method is not part of your interface but instead belongs with the concrete classes. The advantage of polymorphism (and, by extension, interfaces) is in limiting the amount of code that depends on concrete classes, instead depending only on contracts. But, for polymorphism to be actually usable, some part of an application's code needs to create actual concrete implementations and set up any custom configuration (also known as a "composition root"). In your case, this would be when the user settings are known (including which concrete classes are to be used) and the process has been initiated. 

 


The concrete classes will override the 'init.vi' from the DAQ class. They are all a type of DAQ (maybe DAQ is poorly worded), that should all have an initialization, therefore I put the init.vi in the top-level interface. The concrete implementation is then in the override vi's. Or is this not what you mean?

 


@tyk007 wrote:

With regards to your second question. Your issue with the "example code" is not related to polymorphism or interfaces. Don't forget that LabVIEW objects are by-value inherently, so the top "output" object wire data is not the same literal data as the lower "input" object wire. They started as copies but from thereafter they are independent.

 


I know this, hence my question how to share the reference between the two parallel processes.

 


@tyk007 wrote:

In your case you may want to 'share' the SMU object and the data it contains between your parallel processes. That brings you to a reference-based design and then having to handle resource protection and ensuring atomic operation. But I would have to ask - did you actually want two parallel processes?


I don't really understand how to do a 'reference-based' design. I have two parallel processes because generally the output and input devices are physically two different devices that have to be initialized, configured, started, stopped etc.. individually and independently. And if this were the only case, my example would work fine. My problem is when I add a device that is both output and input. Meaning it should only be initialized once, configured once, started once, ... So one of the two processes is not necessary, so how do I determine based on the polymorphic instance chosen, which process to use?

0 Kudos
Message 3 of 7
(1,520 Views)


The concrete classes will override the 'init.vi' from the DAQ class. They are all a type of DAQ (maybe DAQ is poorly worded), that should all have an initialization, therefore I put the init.vi in the top-level interface. The concrete implementation is then in the override vi's. Or is this not what you mean?

My suggestion was that typically "Init" methods can differ between different hardware devices. Rather than trying to create a contract (ie connector pane) that satisfies them all, you could pull the Init method out of your interface all-together. From what you have described you only need it in the one place in your code - where you are creating your concrete objects and you already know the exact type. In which case having Init polymorphic doesn't seem to gain you anything other than tying you down.

 

My advice is to look very carefully as to what polymorphism you use. If something doesn't feel right when trying to implement a concrete implementation then you probably have an LSP violation. For example: looking at your hierarchy, I see a Frequency Generation class there. I assume the setup for generation for an SMU would differ for a FGen. As a simple thought experiment, how did you plan to implement the "Config" contract to support both in a way that makes the sense for users of only one of those classes?



I don't really understand how to do a 'reference-based' design. I have two parallel processes because generally the output and input devices are physically two different devices that have to be initialized, configured, started, stopped etc.. individually and independently. And if this were the only case, my example would work fine. My problem is when I add a device that is both output and input. Meaning it should only be initialized once, configured once, started once, ... So one of the two processes is not necessary, so how do I determine based on the polymorphic instance chosen, which process to use?


By reference-based design I'm referring to sharing data; in this case a LabVIEW class' private data. There are a few ways to do this but the easiest and most readable is to use a DVR (Data Value Reference) of the LabVIEW object. However sharing data adds its own challenges so I wouldn't suggest it lightly.

 

In terms of ensuring that the right steps occur in order: this would be the case for any API that requires multiple method calls to perform a single use case - if order of method calls matters, then you need to either create a means to handle this or to design the need for multiple calls out.

 

Is your example an early prototype of an actual application that will create DMM/Supply objects and execute the relevant functions? Or are you thinking more generically about API surfaces for devices and handling parallel execution scenarios? The advice would probably be different depending on your scenario.

0 Kudos
Message 4 of 7
(1,490 Views)

@tyk007 wrote:


The concrete classes will override the 'init.vi' from the DAQ class. They are all a type of DAQ (maybe DAQ is poorly worded), that should all have an initialization, therefore I put the init.vi in the top-level interface. The concrete implementation is then in the override vi's. Or is this not what you mean?

My suggestion was that typically "Init" methods can differ between different hardware devices. Rather than trying to create a contract (ie connector pane) that satisfies them all, you could pull the Init method out of your interface all-together. From what you have described you only need it in the one place in your code - where you are creating your concrete objects and you already know the exact type. In which case having Init polymorphic doesn't seem to gain you anything other than tying you down.


That's a good suggestion!

 

My advice is to look very carefully as to what polymorphism you use. If something doesn't feel right when trying to implement a concrete implementation then you probably have an LSP violation. For example: looking at your hierarchy, I see a Frequency Generation class there. I assume the setup for generation for an SMU would differ for a FGen. As a simple thought experiment, how did you plan to implement the "Config" contract to support both in a way that makes the sense for users of only one of those classes?


The program only needs to send a sine wave with some parameters. In the settings panel, a different subpanel will be shown to the user depending on the hardware selected. I haven't made any concrete implementation of this, but I guess the settings could be saved in the private data of the same class as in the example. So the class is selected when the user selects the hardware.



By reference-based design I'm referring to sharing data; in this case a LabVIEW class' private data. There are a few ways to do this but the easiest and most readable is to use a DVR (Data Value Reference) of the LabVIEW object. However sharing data adds its own challenges so I wouldn't suggest it lightly.

 

In terms of ensuring that the right steps occur in order: this would be the case for any API that requires multiple method calls to perform a single use case - if order of method calls matters, then you need to either create a means to handle this or to design the need for multiple calls out.

 

Is your example an early prototype of an actual application that will create DMM/Supply objects and execute the relevant functions? Or are you thinking more generically about API surfaces for devices and handling parallel execution scenarios? The advice would probably be different depending on your scenario.


The example is indeed a prototype of a future application. The user will have a settings panel where they can select the hardware they want to use. I don't really have to create an API since this implementation is specific for this application.

0 Kudos
Message 5 of 7
(1,453 Views)

@Basjong53 wrote:


The program only needs to send a sine wave with some parameters. In the settings panel, a different subpanel will be shown to the user depending on the hardware selected. I haven't made any concrete implementation of this, but I guess the settings could be saved in the private data of the same class as in the example. So the class is selected when the user selects the hardware.



Would I be correct in saying then that the "Config" method for the Supply classes is only required to be called once the first time the concrete class is created?

 


@Basjong53 wrote:


The example is indeed a prototype of a future application. The user will have a settings panel where they can select the hardware they want to use. I don't really have to create an API since this implementation is specific for this application.


Can you explain where you think the conflict is occurring with the SMU class? I assume your SMU class is to be capable of both supplying and measuring at the same time. What I find helpful is to note down the steps that occur in each process.

0 Kudos
Message 6 of 7
(1,394 Views)

@tyk007 wrote:



Would I be correct in saying then that the "Config" method for the Supply classes is only required to be called once the first time the concrete class is created?

 


Yes, the config should be called once the settings are applied or when the measurement start.



Can you explain where you think the conflict is occurring with the SMU class? I assume your SMU class is to be capable of both supplying and measuring at the same time. What I find helpful is to note down the steps that occur in each process.


So I'm still completely clueless on how to solve my problem...

My program should have two separate parallel objects, one for output and one for input. They rely on triggers to synchronize each other. If the two devices are different (e.g. an oscilloscope and a function generator) there should be two devices/objects initialized, configured, started etc. Like is shown in the example in my first post. If however, the SMU is chosen, then there is only one device/object, that initializes, configures, starts, measures, etc.

 

So how can I design my software such that based on the chosen hardware I either split up the output and input or have both together in one object?

0 Kudos
Message 7 of 7
(1,343 Views)