From Friday, April 19th (11:00 PM CDT) through Saturday, April 20th (2:00 PM CDT), 2024, ni.com will undergo system upgrades that may result in temporary service interruption.

We appreciate your patience as we improve our online experience.

LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

Should a simulated hardware device be a child or a sibling of the "real thing"?

I'm just now getting started using the Actor Framework and am coming up with some "architectural" questions I hadn't really considered beforehand. Part of my code involves using various hardware devices, which need a custom simulation option- for example, a popup where a user can manually enter the values for an analog input.

 

What I'm wondering now is what the best practice would be for simulated hardware from an LVOOP standpoint. My first go at this had a "Data reader" actor (what I believe is called the "hardware abstraction layer") which had two child actors, a DAQmx one and a Simulated one, where a window would let the user enter the values. This works well and is all fine and good, but when I started working on my second one I started thinking if I should do a Simulated DAQ as a child (override) of the specific DAQ that it's simulating. My gut tells me they should be siblings, but I couldn't find much information on this (searching "child sibling object hardware" just gets me a lot of Google results about toys, haha).

 

As I'm not the first person to need this I am assuming this is mainly a "solved problem" and would love to hear what the best design pattern is for creating simulated hardware devices.

 

Any tips?

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

To be honest you often see both paradigms in use (inside NI let alone with us in the real world). I typically prefer the sibling approach as it is easier to add additional "real" children to the hierarchy in the future however it does make over-riding only specific hardware features to simulate harder. Over-riding the "real" class virtual methods with simulation can let you get away with partial simulations but it becomes harder when you have more than one "real" child. Picking the right option really depends on hedging future change.

 

Another, and potentially better, option is to pull out the DAQ and Simulation into their own hierarchy (with a common base class) and then have your separate concrete Data Reader class that uses a base class instance of the first hierarchy to perform its primary methods (say as a member of the private data of the class). This is known as the Strategy design pattern. It has the advantage of letting you share any common behavior specific to the Data Reader inside that class (eg. analogue value conversion would be common to both real DAQ and simulated values) and also allows you to "swap" the behavior you need dynamically so that you could potentially simulate only parts of your DAQ.

 

The biggest downside to this "strategy" (pun intended) is that you find that your concrete Data Reader class methods tend to just be wrapper calls around the injected behavior plus any common functionality needed; you need to balance this up with the simpler approaches you already described.

0 Kudos
Message 2 of 7
(2,878 Views)

Assuming the data reader class does more than talk to HW, I agree that the instrument driver interface should probably be called from that class, however it still doesnt answer the question - what should that instrument driver class hierarchy look like? I think they should be siblings, just looking at it from an 'is a' relationship standpoint. A simulated HW driver is not a physical HW driver. I know that rule doesnt always stand up to real world examples, but here I think its a good one to follow.. Although I would be curious to hear arguments against it..  

0 Kudos
Message 3 of 7
(2,866 Views)

The only reason in my opinion to go for inherited over-riding behavior is primarily for pragmatism in order to save creating a base class but with the knowledge that there are some axes of change that would break that paradigm - refactoring LVOOP in LabVIEW is hard. In your case I would go the sibling route as this clearly demarcates the responsibilities involved and, while more work, gives you more flexibility in the long run. 

0 Kudos
Message 4 of 7
(2,855 Views)

Just thinking through my own HALs, I think I would actually make my parent class the simulated.  Then all of the child classes are the real hardware.  It will get a little more tricky when talking about DAQ and all of the options available there.  But for DMMs, Power Supplies, etc., making the simulated the main parent should work with little issues.

 

Now I need to find some time to go try that...


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 5 of 7
(2,839 Views)

My first simple answer would be to create an abstract base class for your DataReader and subclasses for the mocked/simulated instrument and the DAQmx class.

 

If you later have many different instruments that share common behaviour such as Reset, then you can consider creating an abstract base-class called e.g. Instrument for that.

 

When creating an inheritance relationship, it should be a "is-a" -relationship as well as "behaves-as". Google "the rectangle-square problem" or "circle-ellipse problem" to learn why. Uncle Bob also explains it in this podcast (I hope its the right link, cannot listen and verify here):https://www.hanselman.com/blog/HanselminutesPodcast145SOLIDPrinciplesWithUncleBobRobertCMartin.aspx). So, is a simulated DataReader a DAQmx? Does a simulated DataReader behave as a DAQmx? The answers to these questions should help you choose whether to use inheritance.

 

You will not find "abstract" classes and methods directly in LabVIEW though. You could do them yourself or e.g. use the G# add-on, which you can find from VIPM. With G#, you can also use dependency injection to easily dynamically load a class, so that you e.g. can define which instrument classes to load in a configuration file. I use this feature in all my projects which are from small to very large, and its really convenient and powerful. One huge benefit of this is that in your code, you have only the abstract class loaded, which just defines the methods but are empty inside, so no instrument specific code is loaded which means you don't even have to have instrument drivers installed. If you would have your simulated class be a subclass of the DAQmx class, this would not be possible. And it also makes it very easy to specify which instruments to mock/simulate which is a great advantage when developing and you don't have access to all the hardware or drivers. Also, refactoring is a lot easier in G# than standard LabVIEW or using Actor Framework, in my biased opinion.

 

You could try these googles too: "labview hardware abstraction layer", "labview hardware abstraction layer abstract", "hardware abstraction layer instrument". Also try adding "uml" to the searches and see the picture results. This has been discussed before, e.g. in  https://lavag.org/topic/15981-hals-uml-and-how-to-abstract-relations/, where some good answers can be found.

 

Certified LabVIEW Architect
0 Kudos
Message 6 of 7
(2,810 Views)

Another option would possibly be: neither.

 

Make an abstract class with the method you require.  This is a given.

 

Then make your hardware class a child of this.  No Problem, we're all OK so far.

 

If you want to be able to only simulate PARTS of your device, make a Decorator for the Hardware class where the specific functions are overridden.  These are inherited from the Abstract class.  Your decorator CONTAINS an Abstract Object (which will be your Hardware class).  You can make one Decorator for each "aspect" of the hardware you wish to simulate.  By cascading decorators, you can choose (at run time even!) which elements of the device you now wish to simulate.  You may even have different types of simulation for a single aspect of the device, this approach lets you mix and match the overrides far more simply than inheritance ever could.  You can even run the same Simulation scheme on different hardware by switching out the encapsulated Class at run-time.

 

It can lead to a lot of "wrapper" VIs, which do nothing else than pass things on to the abstract version.  So maybe even an abstract "Decorator" where that's all it does, may make sense.

 

Shane.

Message 7 of 7
(2,804 Views)