Jump to content

HAL's UML and how to abstract relations


Recommended Posts

Hi All,

I'm in the process of doing yet another re-factor (regulars will probably roll their eyes). I'm trying to implement a HAL and I guess subsequently a MAL. I've looked at the Actor Framework but while I kind of understand it, every time I try and trace an execution path for some task I'm left spinning in circles being constantly led back to the same VIs. I'm imagining something a little simpler such as an OO command pattern, but I'm not sure how I'll handle continuous asynchronous processes yet.

The body of the code will be executed in LV-RT with the UI hosted on windows machines (I'll need some sort of Daemon I guess to manage TCP connections from various people).

Right now, I'm just trying to work on creating a solid HAL.

My main question is, using UML, how does one convey a relationship between instruments that actually depend on each other, shown below is my initial step in blocking out the instruments:

u6BMx.pngThe problem is that the motors (there will be 2) will actually each use a channel from the heterodyne interferometer for position measurements, as will the force transducer. The motors also use the cRIO (2 different channels) to generate analog control signals.

Have I made a fundamental mistake in thinking about how the hardware should be abstracted, should the motor and force transducer inherit from somewhere else?

Area scan camera actually depends on a PXIe FPGA which functions as the frame grabber, how should I capture this dependency given that the FPGA is only used to interface with the camera? Do I need to bother?

As always, your insights are much appreciated!

Link to comment

You are abstracting the application layer (ASL) instead of the hardware layer (HAL).

The drivers that give you digital/analog input/output or even the protocols of communication are the HAL IMO.

The different instruments that use those channels are applications.

I would start with a HW class and input+output children and have the rest inherit from them (if a channel is used in several ways have a child for each use case).

I think NI's example is confusing and lacking explanations from the ASL side.

The way I implemented it is by having the ASL contain LVOOP Command classes for each application.

The each command calls a combination of vis from the application's plugin.

Once loaded the command initializes with the relevant HW from the HAL while the application's class from the plugin is initialized with a DB that shares data between the application's plugin and the state of the system.

This way the plugin app can be developed no matter what HW you are using and the commands can log input and output of actions relative to GUI actions and thus be used also as a recorder of user actions and as a simulation (playback actions and replay/simulate the responses from the HW) method (stub/mock).

This is obviously not the only way to go and the hardest part is not the architecture you are designing, since all it requires is some trial and error, but rather having a team understand the design and maintain it.

Link to comment

Hmmm, ok.

You are abstracting the application layer (ASL) instead of the hardware layer (HAL).

The drivers that give you digital/analog input/output or even the protocols of communication are the HAL IMO.

The different instruments that use those channels are applications.

I would start with a HW class and input+output children and have the rest inherit from them (if a channel is used in several ways have a child for each use case).

I think I understand what you're saying. So make say a generic "HW" class, have "X FPGA" child and then "Interferometer Channel 1" which inherits from "X FPGA" (and I guess uses an accessor to get at "X FPGA's" reference which I'm imagining is encapsulated in "X FPGA" as private data). The thing then, is that every child needs to know about the parent's private data, which brings me to a question that's been puzzling me. If children inherit, they don't necessarily have access to the data that was defined as part of their parent, but when you hover over the child's class wire, the parent data structures are listed in there. If I call an accessor (a parent's accesor?) on that wire, what do I get? The actual state of the parent's data? Or, the default value of said data?

The problem I'm wrestling with is how to encapsulate an FPGA reference, who should have scope on it etc.

The only way that makes sense to me mentally is that the FPGA reference for each type of FPGA is private data of a class say "7813R FPGA" and reading the FPGA indicators and buffers are methods of the class. The problem with this though (from my initial tinkering) is that encapsulating the reference, even as a type-def, doesn't seem to cascade that information down to the methods. So when I try and unbundle the reference from the object it doesn't carry any information about indicators to be read etc. (This is more specifically a question regarding FPGA's and OO directly, but could be read as how to encapsulate an FPGA).

Link to comment

Ok, I've figured out how to encapsulate the FPGA Ref as class data such that it propagates. So I've been tinkering with the idea of calling the read of each indicator as separate method. Not sure what the ramifications of working like that would be though.

Link to comment

The thing then, is that every child needs to know about the parent's private data, which brings me to a question that's been puzzling me. If children inherit, they don't necessarily have access to the data that was defined as part of their parent, but when you hover over the child's class wire, the parent data structures are listed in there. If I call an accessor (a parent's accesor?) on that wire, what do I get? The actual state of the parent's data? Or, the default value of said data?

The actual parent-class data of the object. An object of a child class is a collection of clusters, one for each level of class hierarchy. You can call any parent-class method on any child-class object. Note that I am being careful to call it a “child-class object”, not a “child object”, as the whole “parent/child” metaphor really only applies to classes. No actual object is a “child” of any other object.

Link to comment

In other words, it doesn't make sense to have methods on an "X FPGA" class implemented as children inheriting from that class?

I don't understand how to encapsulate the functionality of an FPGA, as it may provide disparate functions. For example, one FPGA I have connects to a cRIO providing AO/AI and it also connects to an interferometer via a custom connection and provides interferometer counts.

If I implement all the functionality as methods on a single FPGA class, then what's the point? I'm not taking advantage of dynamic dispatch at all?

I've tried to parse out what O_o means by

I would start with a HW class and input+output children and have the rest inherit from them (if a channel is used in several ways have a child for each use case).

But I'm stumbling on the practical implementation of this. I tried to set it up so that the Parent FPGA class has a dynamic dispatch method called "read indicator" which is over-ridden by the child, but first of all I get a broken run arrow and it says the con-panes are different when I try to actually implement any form of read and output (I thought child classes could over-ride and extend the methods of their parents).

Secondly, it doesn't make mental sense to me at the moment to instantiate different children for each method because the object of the parent holds the initialised reference (of which there can be only one, no child implements an initialise method). Then unless the initial parent object is passed to the children as an input, how can they possibly know about the "actual" FPGA?

See, I thought I understood something, but the devil is in the details and he's a big dude hiding behind a little detail.

Link to comment

AlexA, did you solve your problems already? Post a new UML if there are still some design problems.

As for the video crelf posted about, they show an encapsulation of access to a DMM driver while adding an emulation version.

It is the basic idea but it is not a HAL example since the DMM itself is not a type of a generic HW and there is no clear separation between ASL and HAL.

Thus, IMO, it is a bit confusing for anyone that wants to implement a HAL for the first time.

I would refer you to the official HAL 2.0 document and examples at NI yet I both think you tried it already and I think it is also lacking some information about the ASL level and the MAL+plugin integration.

Moreover, most of the examples are written for NI HW and not for a generic digital/analog input/output and a FPGA combo that implement some virtual instruments.

Good luck.

Link to comment

Hi 0_o, thanks for your continued interest.

I'm playing with actor framework, on top of working out how a HAL should work. The following is all I have for a HAL UML diagram at the moment.

YHcC3.png

As you can see, I'm kind of unclear whether to think about the camera and its FPGA as one hardware entity (I really think I should, you can't really abstract "FPGA" type hardware in LV, due to needing a strictly typed reference in the class data).

Link to comment

I'm in the process of doing yet another re-factor (regulars will probably roll their eyes)...

Actually, anyone who has gone through the process of learning object-oriented design understands exactly what you're going through. Don't get discouraged. Learning all the ins and outs of OOP takes time, and the best way to learn it is to write code and then figure out why it doesn't quite allow you to do all the things you thought it would. It took me at least a year of using LVOOP almost exclusivly before I felt like I was more effecient using it than traditional LV. There is much more information and help available now than there was three years ago so hopefully it will not take you as long.

  • Like 1
Link to comment

The following is all I have for a HAL UML diagram at the moment...

Is there a particular reason you have an instrument base class? It's not really providing much of an advantage for you and inheritance relationships are relatively restrictive and inflexible compared to aggregation. This is how I implement a HAL using aggregation and adapters. (It's similar to what Mikael does, except I eliminate the instrument base class.)

post-7603-0-17646100-1343935011_thumb.pn

Link to comment

Let's talk more about designs at NI week.

Absolutely. It's the primary reason I'm going to NI Week.

I use the instrument base class for my Factory Create function.

I noticed that, and that your TestSystem actor maintains an array of Instrument objects. I guess I don't see any point in creating a parent instrument class as a standard development pattern. The point of an Instrument base class is that it allows you to treat any specific instrument class generically. But your Instrument base class doesn't have any methods to control the instruments. You're still going to have to downcast to a specific abstract class in order to call any instrument methods in your application code.

The one thing the Instrument base class does allow you to do is store all the instrument objects in a single array. Whether or not the value in that ability is worth the contraints and extra code required for doing it is a subjective evaluation. In my projects it hasn't been worth it. It's much cleaner and easier for me to put separate VisionCamera, PowerSupply, and TemperatureChamber abstract class constants (or arrays of constants if multiple instances of an instrument type are needed) in the TestSystem class.

I understand why someone would use a factory class, but I don't understand why that factory would be a parent to the classes it's creating.

@Alex

Your UML diagram shows a few methods that presumably all instruments will implement: Initialize and Shutdown. This does allow one to iterate through an array of arbitrary instrument objects and initialize them or shut them down. From a practical perspective I haven't found that a particularly compelling reason to impose an inheritance relationship on the classes in my projects. Both operations are sufficiently rare that any advantages gained by the ease of calling either of those two methods is outweighed by the hassle of calling all the other instrument methods. YMMV.

Also, for the Initialize method, each specifc object needs to be configured with all the appropriate information prior to calling the Initialize method. If I have to make instrument specific calls to set up each instrument object, is there an advantage to building an array and invoking Instrument.Initialize on the array (which then dynamically dispatches to each instrument's Initialize method) instead of just calling the instrument's Initialize method when setting it up with the correct configuration. Perhaps sometimes*, but in my experience not usually. Again, YMMV.

In general, I have found my apis for classes that actually implement functionality (as opposed interface classes that delegate work to other classes) are much more readable when the instrument's Initialize method has connector pane inputs for all the arguments related to initializing the instrument. Obviously each instrument is going to have different kinds of parameters for the Initialize method, which prevents you from creating an Initialize method in the base Instrument class.**

The way you have your class hierarchy set up will likely lead you down the path of putting application specific code in your instrument classes, which makes it harder to reuse them somewhere else. Or maybe you'll be tempted to create abstract methods in the base class that are only applicable to some of the child classes. Both options violate OO design principles and if you find yourself doing them it may be worth your while to investigate other solutions.

-Dave

(*The one time I can think of off the top of my head where having a Initialize method in a single Instrument parent class might be beneficial is if you have lots of different instrument types and need to reinitialize them frequently. For example, on a test sequencer where you want all the instruments to have factory default values at the start of every test.)

(**You can overcome this problem to some extent by adding a LVObject constant to the Initialize conpane and labelling it "Configuration," but you have to do run time downcasting to use the specific config object and you lose compile time type checking.)

Link to comment

Sorry for the late reply.

I wish I was a bit less busy and had time to go to NI Week too you bastards :worshippy: Yeehaa

Will there be a recording of all the sessions for the common people this time?

As for the topic, I’m using FPGA via VHDL, thus, I don’t have enough experience with abstracting FPGA directly through LV.

For me abstracting the FPGA includes abstracting the communication (TCP/IP) and the string commands (Command design pattern (low+high) + DB + testing + plugin).

The way I think the general question should be tackled is:

  1. List the hardware and protocols being used from your computer from the nearest to the farthest. The closer or the more instruments depends on the device/protocol the more fundamental it is and thus lower in the HAL.
  2. Ask yourself what might change sooner and multiply this list with the list from (1). The more (fundamental * changeable) the device/protocol is the more you need to abstract it. In MichaelH’s UML the communication protocol is the most fundamental block that might change from RS232 to TCP/IP or to a DAQ card. All the instruments need to read/write to one of those blocks and you want to be able to change it easily without touching the rest of the code. A DAQ card might have a driver update with new LV building blocks even while the card is still the same. It will be nearly impossible to change all the methods that use those fundamental functions. In my opinion even file access should be in this HAL low layer since this is a method you are using to access your HD. I have an abstraction layer that contains advanced/basic TDMS + basic file access + TCP/IP + RS232 along the DAQ to abstract all my read and write. I allow slow OO design but I take care that once I get to the direct read and write to the IO it will work as fast as possible. This way you solve the communication protocol once and have lots of code reuse while never spending time on reinventing the wheel.
  3. Inheritance between HAL classes should be added in one of two cases: a) Most of the vis in it are the same. b) You want to impose a style and structure for your team in order to make the code readable and maintainable for future changes (you could easily add tests or any other function to a well thought hierarchy. In Daklu’s case it will take shorter time to design but the code will be much less configurable and if one day he will need a new common function it will be much harder to have some code reuse. Since no one knows the exact future functionality I find OO to be a good choice (it is like planning for a bad weather, however, not everyone agrees that it is wise to buy an umbrella in the summer even though it is much cheaper). Personally I find the task of enforcing a clear architecture on a team that will be maintained for years to come even when team members are changed to be a very important task and the hardest to achieve. Though Daklu might disapprove, I think aggregation and adapter design patterns make the code less decoupled and not more decoupled.
  4. Each instrument should be associated with a DB Class and all the DB classes should be maintained by the main up. A DB Class will contain, among other basic states, configuration parameters for basic cases and state enum so that initialize will start immediately most of the time without manual changes once you loaded the instrument’s DB. I prefer this upon requesting the data through messaging since the DB DVR allows for better locking and prevents more race cases and deadlocks IMO while allowing for a much more flexible classification of DBs.
  5. Higher level instruments that depend on the lower level instruments must use the abstraction layer and never a direct access to the low level functions. And they are actually part of the Application Abstraction Layer and they contain a list of commands with no drivers or communication protocols. A general rule of thumb: the more fundamental an instrument is the less classes it should contain.
  6. Code reuse between unrelated instruments should be handled by the framework (MVC+UI Framework for example) and not by the HAL.
  7. Once you decide on a design the first step is to test the most general case with communication and DB sharing between the instruments + testing + trying to change a fundamental instrument. Once verified document the design, give examples/templates and start praying that your design doesn’t lack any issue like the singleton behavior of dependent resources or any time constraints or some parallelism issues. Just remember, as long as all the problems you have are in implementing the design and not in changing it you should be thankful! This OO design will protect you from breaking it and you could easily see that each deviation results in a broken arrow. Instead of being angry :throwpc: that you run into a wall each second be grateful that you have a home to live in :worshippy: and open your eyes.

In the case of the 7951R FPGA and the Area Scan Camera, I interpret the FPGA as a DAQ I use to control several instruments. If you apply the logic I described above you’ll see that the FPGA is more fundamental and changeable. What will happen if a year from now you will have to replace this FPGA with a new version while keeping the same camera. What will happen if you’ll need to change to a different communication/control that is not FPGA? What will happen if you’ll plug a different camera to this FPGA?

When I ask what will happen I’m not talking about the camera functions which you could easily rewrite but rather to your main code that calls those functions.

If this FPGA serves only your camera you can see them both as a single instrument, even though the FPGA is more fundamental, and you should just abstract the FPGA calls to the camera. It is a game of give and take. The main issues with abstraction layers is readability and design time so you shouldn’t abstract if you are sure this is a risk you can take. If you ask what the main risk is, well, it is a business logic issue. Any product must change its instruments (like the camera) every 3-4 years since there are better instruments that cost less and your instrument is out of stock. The more time it will take you to release an upgrade the slower you could react to your competition. However, the more time it will take you to design the more money you need to jumpstart your product before you run out of money.

Good luck and happy NI Week.

P.S. – If it wasn’t clear by now… I’m SOOOoooooo jealous of the NI Week participants.

Link to comment

I've just skim read your post and there's a lot of concepts there that I need to get my head around, but I wanted to thank you in advance for taking the time to put it down on paper! I'll try to get it all straight in my head over the next few days.

Link to comment
  • 5 weeks later...

I was reading through some old posts and saw my name... so of course I had to reply. :)

In Daklu’s case it will take shorter time to design but the code will be much less configurable and if one day he will need a new common function it will be much harder to have some code reuse.

I don't understand why you think this. If I want to use the Fluke DVM class in another app it is completely portable. If I want to replace the Fluke DVM in this app with a Radio Shack model, it's as simple as creating a Radio Shack Adapter child class that inherits from the DVM Interface class and having the RS Adapter delegate all calls to the Radio Shack DVM class. What kind of reuse is hard to do?

Though Daklu might disapprove, I think aggregation and adapter design patterns make the code less decoupled and not more decoupled.

Disapprove? Not at all. Disagree? You bet, at least until you convince me otherwise. Can you explain your reasoning? (I assume you're contrasting the adapter with direct inheritance?)

Link to comment
  • 2 weeks later...

Hey, I'm back.

Sorry for the disappearance, I’ve been doing a SWOT analysis in a marathon of some management meetings. H-E-L-P-!-!-!

First let me start with NI Week... how marvelous.

My wish of being there was half complete once so many great talks were recorded.

Thanks Mark Bella!

I love you guys and wish to see you there next time.

Now, Daklu, my reasoning is very simple: it is hard to impose a coding style on a large or even medium size group of programmers with different levels of OO experience and even between generations of programmers that come and go. Most places don't even have an architect. The result is layers of code that each do approximately the same stuff but a bit differently since the documentation is never enough and each has its own bugs and together it is a one big mess. The distance from there to a full spaghetti code is not that much.

Aggregation and Adapters, though requires less coupling on the surface, down below requires a much deeper coupling.

It is the coupling between the code and the programmer who wrote it which is very hard to handle.

How can you measure such a thing in respect to decoupling a system?

I know each of us would love to be permanent in a work place and coupling is a great way to achieve it but it is not a good BI approach.

Oh, and if someone thinks that I say aggregation and adapters are evil, you got me totally wrong. They are much better than what you usually see around the best written code. I just said that for me it is too open and I like a code that makes it very hard for a programmer to deviate from the architecture.

Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.