06-05-2013 01:45 PM
No, we have no drivers. There are only the actual, physical instruments (with serial/GPIB ports) and a manual with a list of commands to send the instrument to get the response we need. The objects interface with the device and thus become the driver.
Sounds like MAX won't work here, then. I see "create task" when I right-click on an unrelated PCI MIO card, but not when I click on a serial port.
06-05-2013 02:36 PM
@crcarlin wrote:
No, we have no drivers. There are only the actual, physical instruments (with serial/GPIB ports) and a manual with a list of commands to send the instrument to get the response we need. The objects interface with the device and thus become the driver.
Sounds like MAX won't work here, then. I see "create task" when I right-click on an unrelated PCI MIO card, but not when I click on a serial port.
Create task would be if you were using NI hardware. I think an incorrect assumption was made in terms of what you were using when that response was posted.
06-05-2013 02:36 PM - edited 06-05-2013 02:37 PM
double post
06-05-2013 06:19 PM
crcarlin:
I don't know if my answer will help.
I believe that you are not running up against a limitation of LabVIEW but a limitation of language. I do not believe there is a GOOD solution to your problem, as you've described it, in *any* programming language. I'll run through the options and highlight where LabVIEW does best, and then leave you to decide how to proceed.
You might be able to get something working in a programming language that supports multiple inheritance. Certainly you would be able to define GetTemperature.vi once on one base class and then inherit it onto as many child classes as you need. You are quite correct: LabVIEW does not have multiple inheritance. You said, "I see other forum threads and articles from NI saying multiple inheritance isn't needed because anything that requires it can be done in other ways. So, I was hoping I was missing something." I strongly suspect that what you are reading was written by me. 🙂 And if so, I was saying although there is a real problem that does need solving, and although multiple inheritance looks like the solution to that problem, in reality, many people have tried to use multiple inheritance and found that it creates more problems than it solves, resulting in some remarkably horrible code. It's great for some simple cases, and those simple cases look so inviting. But it is trivially easy to get beyond those simple cases. In your case of combining phsyical devices, I would easily see problems as soon as you had a device that could get temperature and get pressure, but not at the same time. As soon as there is *any* interaction between the two base classes (like a physical device only being available for one operation at a time), multiple inheritance tends to get really complicated realy fast.
The evolution of computer science lead the world's programmers to interfaces. LabVIEW also does not have interfaces. I wish it did, and it will someday, but it doesn't today. BUT, honestly, given your description of your users, I don't think they'd accept the interfaces solution. Why? Well, your users are expressing an opposition to multiple objects for the same physical device. Interfaces would still result in multiple object wires on the diagram that all refer to the same device. If you had your array of Devices, you could not call a "Get Temperature.vi" method unless you first tried to cast (To More Specific) the Device into the Thermometer interface. So any attempt split off "these are my temperature sensors" and filter them out of the array of devices would give you the original device object and the temperature sensor object -- both for the same physical device. Of course, you could just leave them all in the Device array and do the cast as needed. The irony of that solution is that you would have a runtime cast, and if the cast succeeded, you'd call the method. If it failed, you'd return an error or silently skip it -- exactly the same behavior that you have with the "all methods on the base class" solution that you have today!
I do want to mention that there are some highly usable solutions that come from having multiple objects that represent different faces of the same physical instrument (for example, the entirety of DAQmx is oriented around different references for the channels and the timing and various other aspects). But if your users have rejected that, then that's that. I mention it only if you are looking for a model to use to convince them that it might not be so confusing.
All of that leads us back to the solution you already have: putting all the methods on the base class. Given your problem environment, that *is* the right solution. There is not any logical way that I konw of, regardless of the features we might add to LabvIEW, that would avoid that. Either the method you want is defined on the common type (thus you can have only one typed reference to use to call that method) OR you have some way of representing the different facets of your physical device as separate objects that have separate capacities.
crcarlin wrote:
> So it looks like here's an example of where LabVIEW is coming up short for not providing that functionality
I hope I've walked you through enough backstory to explain why I don't think this is a shortcoming of LabVIEW. Yes, we need interfaces, but I don't think they'd help you. And the only solution that I can come up with is one that LabVIEW can already provide.
Good luck.
06-05-2013 10:24 PM
AristosQueue, I don't see that at all.
Perhaps the root of the issue is indicated in your talking about multiple objects for the same physical device. Frankly, I have absolutely no idea why you say that. A HUGE part of my reason for using objects here is precisely so that there AREN'T multiple wires for the same device.
I don't know what else to say here because I'm so utterly befuddled as to what you're talking about 🙂 And I don't mean to be insulting or harsh or anything; I appreciate that you wrote a long reply, but I honestly don't understand what you're saying.
I don't even know why you're talking about casting since the cast is implicit. LabVIEW's dynamic dispatch takes care of it just fine.
06-05-2013 10:43 PM
If you get runtime errors for measurements (methods) that aren't implemented, does that mean the customer is changing which class is being used? While I appreciate the device-class + measurement-class approach, wouldn't it be simpler to just make one class for each type of instrument? If the GPIB/VISA commands are similar across instruments, put the commands in their own class.
Reading AQ's post and your response makes me wonder: how would you do this in the language you're thinking of?
06-05-2013 10:52 PM - edited 06-05-2013 10:53 PM
Todd_Lesher, this is so intuitive to me.
For example, right now I have a class for a LS218 that reads temperature using GetTemperature.vi. If my LS218 breaks, I can swap in my LS330--same company, different commands--and by swapping only the object on the block diagram, all of the GetTemperature.vi calls are updated to send the correct commands.
They change from the Get.Temperature.vi of the LS218 class to the GetTemperature.vi of the LS330 class.
But what happens when my LS330 breaks? I might swap in an MKS230, which is primarily used to measure pressure but can measure temperature too. I'd want the exact same thing to happen, with GetTemperature.vi to change to the GetTemperature.vi defined by the MKS230 class, which would be entirely easy, seamless, and simple if there was multiple-inheritance/interfaces.
In short, this is the obvious behavior. Does the class provide the method in its inheritance chain? If yes, execute it. If not, break the diagram. The restriction on inheritance only complicates that algorithm.
06-06-2013 02:21 AM
@crcarlin wrote:
I can swap in my LS330--same company, different commands--and by swapping only the object on the block diagram, all of the GetTemperature.vi calls are updated to send the correct commands.
And how does this swapping ACTUALLY happen. You drop a constant on the Block Diagram, right? Well if you drop a constant for a class which CONTAINS both temperature reading and pressure (using the aggregation approach highlighted previously by others), you'd need a single extra VI to get the temperature module from the object and the rest is easy peasy. If that's the concern about "multiple objects for a single device" then the requirements are getting a bit silly. And yes, I appreciate that being able to solve silly problems from customers is a valid request, but maybe you just need to sell the obvious solution to the problem at hand instead of throwing your hands up into the air and blaming LabVIEW.
And also, I would love Interfaces in LabVIEW (And abstract classes) and a host of other things. But I still manage to get by on what we have. I fail to see why you can't also.
06-06-2013 02:27 AM
@crcarlin wrote:
AristosQueue,
I don't even know why you're talking about casting since the cast is implicit. LabVIEW's dynamic dispatch takes care of it just fine.
The cast is required so that the interface type of the object being addressed can be determined. Since you can theoretically define many many interfaces for a single object, the compiler (and the IDE) need to know what you want to do esaxtly with the object at hand. This is where a "cast" (perhaps a different word would have been better) comes in and allows the IDE to properly interpret the functionality of the object int eh context you are currently trying to use it.
So it's similar to a cast but it must be performed explicitly, it's not automatic like you seem to think. The big advantage of this would be allowing different interfaces to share methods with the same name whereas multiple inheritance (Essentially what you're looking for) cannot do that because you quickly get name conflicts.
06-06-2013 04:24 AM - edited 06-06-2013 04:40 AM
There are a few drawbacks to that, Intaris.
Firstly, I disagree that the requirement is silly. To me it's natural, reasonable, and intuitive to insist on one object per device, both for user reasons and for OO design principles reasons, so any additional hoops end up seeming silly to me... which is how we got here.
Secondly, when you unpack the individual behaviors from the instrument with your one extra vi, suddenly you're faced with more than one wire representing the object. Sure, we can have references inside the behavior so that there's really only one device object, but now we've (1) broken the encapsulation because the user needs to know the implementation is protecting him, and (2) thrown a curve ball to good programming because my hammering in of a "Don't branch wires!" mantra will seem to have a giant exception.
So yeah, I find this approach of creating sub-objects based on bits of behavior instead of is-a relationships to be the silly requirement, and it's a requirement being imposed by LabVIEW instead of any notion of good design or user want.
There's no need for explicit casting at all for this. Whether it's done through inheritance or interfaces, everything can be clear at compile time through normal most-recently-overridden and most-overlap patterns: add a temp-pressure meter to an array of pressure-voltage meters and the output is an array of pressure meters. Try to call GetVoltage.vi on that array's members and get a compiletime error since a member of the array doesn't implement that, which caused it to be an array of only pressure meters in the first place. Natural, easy, and consistent.
Well, that kind of assumes a multiple-inheritance or interface solution. (By the way, I don't buy the "quickly lead to name collision argument. I've read it; I get it; I don't think it's a big deal to decree that one may not use two interfaces/parents that overlap in a name. We're used to such restriction elsewhere in LV.)
Another solution, sort of a compromise one, woud be to create real abstract methods: allow a vi to exist broken in the inheritance tree so long as there aren't any "call parent" calls. I'm not sure this would even have to be explicitly marked as abstract. Then I'd just have one Meter class full of broken VIs, with children overriding with functional ones.