LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

RS232 driver for slightly different submodels' command/parameter set - best practice for OOP approach

Hello!

 

I am still quite resisting to dive into OOP, and now I feel one particular RS232 driver which I am developing based on a manual, could be a good candidate to get pimped with OOP (very simple serial comm with just some functions, CR+LF double term. characters). I only implemented a few of the required functions yet, and I started with a Class. The object of the class is a control with 3 elements so far:

Laser_Quantum_PSU.lvclass_Laser_Quantum_PSU.ctl_FP.png

There are 5 submodels from this company, and I would like to make the driver be general, so working with all. The enum "Control Mode" has 3 values: "Current", "Power" and "Mixed". One submodel is always in mixed mode, all the others only support the "Current" and "Power" modes.

Right now I only use the Class as a "protected cluster wire", so not using the real power of OOP yet! So I just unbundle the PSU type from the CLass wire for example, inside a subVI, and decide what RS232 string command should be used, what baud rate I need, or if the function is a no-op via Case structures...See some snippets below. I also attach the Class zipped up in version 2015.

 

Question: as I understand, I should create inheritance, so there would be a parent "Laser Quantum" class, and several (5?) children classes with subVIs overriding subVIs defined in the parent class? Could you give me some hints which direction should I start to modify my existing Class? I feel a bit confused to be honest 🙂 Do I need dynamic dispatch here?

 

Laser_Quantum_PSU.lvclass_Initialize_BD.pngLaser_Quantum_PSU.lvclass_Get_Firmware_version_BD.pngLaser_Quantum_PSU.lvclass_Set_Control_Mode_BD.png

 

0 Kudos
Message 1 of 9
(2,943 Views)

Sounds like you're right in every way, pretty much.

 

My understanding (perhaps wrongly) is that your parent class should in the purest terms be a no-op class - all of the implementations of specific actions are empty. You can use the template method pattern quite effectively if you have some parts that need to be called in the same manner each time.

This might be effective in your Initialize.vi - you can call "Get Baud Rate", "Get Parity", ... (or "Get Init Parameters" and use a cluster), each of which are Dynamic Dispatch, then pass it to the property node in the parent "Initialize.vi", which in this case would be (probably public) Static Dispatch.

The "Get Firmware Version" could be another candidate for this - make "Get Version String.vi" dynamic dispatch and have the SMD6000 class return "VERSION", whilst the parent gives the default "SOFTVER?". (Edit - to be clear, in this case, you don't need to implement the children methods for the other children - only for SMD6000).

 

 

If you have the same implementation in most cases, and maybe different in one, you can consider placing the entire implementation in the parent, then overriding only in one child - this will cut down the number of VIs you have to write.

I've often started with this, but have been moving away from it recently after discovering that it can make things harder to test, since I don't have an "interface" class - my interface is my implementation and the child is a modified version.

 

I've also gotten some mileage from the ideas described by the Non-Virtual Interface style (method? pattern?). Here the idea is basically to make the public methods static dispatch and have them contain Protected access scope dynamic dispatch methods a la Template Method. By preventing callers from accessing dynamic dispatch methods, it hides away the children a little better (because they typically then have 0 public methods).

 


GCentral
0 Kudos
Message 2 of 9
(2,910 Views)

The quick answer:

See that case structure where you have selections based on the PSU Type to choose which command to send?  That should be a method using Dynamic Dispatch.  You will also need to update your initialization to load the right class based on the PSU type chosen.


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
Message 3 of 9
(2,908 Views)

A possible example is attached - I just used your VIs in a couple of places and then carried out the splitting into hierarchy step.

 

Going somewhat against my previous comment, most of the code is the in the parent - it seems like your code is similar for most types, then differs for just one. Probably this is due to the fact you posted a demo rather than a full driver, so I'd still suggest avoiding putting too much code in the parent that isn't used by ALL children (both now and possible future children!) - overriding to ignore is annoying. Writing an accessor method first will save time later, I think.

 

Updating for crossrulz's message - I interpreted the PSU type as an effect of the model choice and guessed you'd drop a class constant of the correct type. If you instead choose a type, then you should do as he said and add the constants to the BD of the Init method.


GCentral
Message 4 of 9
(2,907 Views)

My personal 2c.

 

I would ponder the option of separating out the communications layer from the logic layer completely.  Why would one do this if both instruments use RS-232?

  • By having a communications layer defined separately, you can implement tests, make remote device calls (via whatever method you like) and do lots of other things which you probably don't think you'll ever need until you do.
  • If you want to add another device in future which uses USB, or interfaces via DLL, but which supports the same commands, you only need to re-implement the comms layer and simply re-use the logic layer.

Just my thoughts having been trhough this process several times in the past.  Identifying the device communication and the device logis as seperate parts allows separating them into distinct objects to aid flexibility, testing and code re-use.

Message 5 of 9
(2,902 Views)

Thanks all for the ideas! I will play with the things for a while, might be back later with more questions...

0 Kudos
Message 6 of 9
(2,891 Views)

I try to work on and understand the example project posted by cbutcher. As I understood, I have the parent class "Laser Quantum", and two children classes: "FPU model" and "SMD6000". The FPU model is just (so far) an "empty" class, with no methods, so inherits all methods from the parent? Like using the general "Get Version Command.vi".

It confuses me a bit, that there are "Child Methods" folder and VIs under/inside the parent class. So I guess these are the so called "Dynamic dispatch VIs"? So if I want to override these general methods in a child class, I have to create VIs with the very same names and connector pane, correct? So if I want to create a new child class (like class for another submodel):

  1. I have to create first a new class, and set it to inherit from the parent (Laser Quantum.class)
  2. Save it to a subfolder
  3. Right click on this class, and select "VI from Dynamic Dispatch Template".
  4. Save it with the same name what it should override?
  5. Something more to do? See the attached project with the new "another_submodel.lvclass"

I get more and more confused, beside I am really not familiar with the IDE settings, where to find the different inheritance and class settings for VIs etc... 🙂 Need to learn more...

 

Extra question: how should I modify the Initialize public method that the user can select which submodel is used during runtime? See snippet below (I also zipped and attached the modified project):

 

Laser Quantum.lvclass_Initialize_BDsdsdsd.png

 

 

0 Kudos
Message 7 of 9
(2,876 Views)

The quick way to create a new override is to right click on the child class, or a virtual folder inside the class, and choose New > VI from Override 🙂

As you pointed out, it must have an identical connector pane and name. The layout on disk is irrelevant (but since the names must match, it can't be in the same directory).

 

I used side-by-side directories just as a personal preference, they can be wherever you want. I'd suggest keeping them close, but you might prefer for example to have 

Application ...

... Parent Class 

... Parent Class/Child Class 1

... Parent Class/Child Class 2

or similar (I figured child classes aren't members of parent classes, so I put them alongside. Typically they're in a separate directory which for you might be "Laser Driver" or similar (although a laser driver is a specific thing, so maybe not that name...))

 

To follow crossrulz's suggestion - do exactly like you've done in the snippet. Drop a block diagram constant for each specific child class in a case structure, then wire it to the Laser Quantum out terminal. Delete the input terminal and use the PSU type input instead.

 

In my original understanding, "Laser Quantum in" is already holding the specific child class, because you set it on the caller's block diagram (the more I type, the more I think the alternative is better...)

 

Regarding dynamic/static - dynamic inputs are with a kind of hatched edge on the connector pane. It's in the same section as choosing the type (Required, Recommended, Optional). If you have a dynamic input, you can still have a Recommended (i.e. static) output. However, you can't have a dynamic output if you don't have a dynamic input.

 

With regards to the IDE class settings, going to the properties of the class in the IDE allows a couple of useful tabs, most significantly (at least for me) Inheritance (which I guess you already found) and Item Settings (which allows you to enforce certain behaviour on children). These behaviours are

a) Must call parent - this forces any overrides of a specific subVI to call the parent implementation using Call Parent Node. This can be useful, but can also be a little confusing, especially because typically (at least when I use it - maybe I have poor implementations) the code requires it be called in a specific part of the child (typically first, or last). Since this isn't enforced, and since it's not easy to accurately document the requirement, I have to pay careful attention to keep the VIs doing what they should be. This is one of the main reasons I've found to like the template method.

b) Must override - does what it says on the tin. All children must provide an override for this VI. This is pretty unambiguous and can be helpful, but equally can be annoying if overused. There's also a tickbox (on the Inheritance tab) allowing you to transfer these requirements (for multiple level hierarchies).


GCentral
Message 8 of 9
(2,867 Views)

As a brief follow-up; in my BD comment I wrote there was a discussion at Delacor - that turns out not to be true. The article I had in mind was written by Chris Roebuck at http://www.walkingthewires.com/2016/02/15/just-passing-through/

Sorry!


GCentral
0 Kudos
Message 9 of 9
(2,864 Views)