Jump to content

Alternative to FGV & classes


Recommended Posts

Hello there,

I have written a modular data acquisition & analysis tool. I have a class responsible for reading data and spitting it out onto a queue as a waveform array. The contents of the array depends on which channels are being logged - one time there might be 4 pressure and 8 accelerometer and another time 4 accelerometer and 2 pressure.

Now, to get me started I just assumed that I always had the same channels in the waveform array, so my pressure analysis module knew that it needed to get elements 4,5 and 6 from the array for example. But, I now what to add the ability for the analysis modules to be told by the daq class where the data is.

I know that each analysis module could do it by looking at the NIChannelName property for each element in the waveform array but that has its own issues.

I could have an FGV which the daq class writes to (number of channels, locations) and the modules read from. But I was wondering if there is a way I can get these modules access to the daq class data method readChannelPositions(). I think the problem I've come up against is that the modules don't have a reference to the class and I can't pass a wire in to them. I guess this is what people refer to as by-reference versus by-value?

Are there any helpful tips you can offer please? It would be much appreciated.

Thanks,

Martin

Link to comment

Hi Martin

I am sure there are more elaborate ways, but one way I have dealt with this before, and may be work here? is the following:

Create a Channel Parent Class that has a property Type (Pressure, Accelerometer) and another property Data (waveform in this case)

Create a Channel Manager Class which contains (and manages) an array of Channels (essentially a Map)

Channel Manager has a method by which it can return an array of Channels that match a Type input i.e. return all Pressure Channels

Then you can just pass this into your analysis module knowing that the Channels are only of one Type

Cheers

-JG

Link to comment

I have written a modular data acquisition & analysis tool.

The answer depends somewhat on your goal for your modules. I'll assume your two main modules are daq and analysis. Do you intend to be able to reuse either of them in other apps independently of the other? If so, there can't be any dependencies between the two modules. On the other hand, if this is all application-specific code then coupling them together somewhat will help you get the job done faster.

Jon's suggestion is good and will work. The downside is that by using the channel class into your analysis module, the module becomes dependent on that class. If you want to reuse the analysis module in a different app the channel class hierarchy needs to be duplicated too. This isn't necessarily a bad thing--it depends on what your goals are. If you do go that route I'd suggest making the channel class hierarchy part of your ChannelAnalysis.lvlib to avoid missing links when reusing the library.

Being aware of and managing dependencies between components is critical to building a successful system. It's one part of "managing the seams" of your application. Make sure you take time to consider each component as an independent entity, ignoring all the other components in your system. What does this component do? What will it need to do in the future? What are its dependencies? Are those dependencies okay? What does the public api look like? Is it clear? Is it complete? (Can component users do anything they reasonably would expect to do?) Are the methods distinct? (Is there overlapping functionality in any of the public methods?)

If you want a component to be completely independent of any other code, your public api can only expose types that are either native LV types (string, variant, LVObject, etc.) or types that are included in your component's library.

I have a class responsible for reading data and spitting it out onto a queue as a waveform array.

If I'm understanding your problem correctly, you've discovered you now need to send information about the waveform along with the waveform data itself, but you can't because the queue is typed for waveforms. Using 'traditional' LV techniques this was often accomplished by flattening the data to a variant and adding attributes for the meta-data. Your analysis component then reads the variant's attributes and acts according to what it finds.

Personally, that doesn't provide enough type-safety for me. I find the explicit contracts of an OO approach are easier to work with from the component users point of view. I have a generalized queue-based messaging library where all my queues are typed for Message.lvclass. In your case, I would have created a Message subclass (call it "WaveformMsg.lvclass") with accessor methods for carrying the waveform data. When it came time to add meta-data to the message, there are several options. I won't go into too much detail here, but feel free to ask questions if you're interested. They all accomplish the same thing.

1) Use the same WaveformMsg class for each waveform configuration, but give the object a different message name that reflects the configuration. This is what I usually do. It provides a balance of flexibility and type safety that I'm happy with.

2) Add fields for the meta-data to WaveformMsg and generate the accessor methods. Use the accessors in the analysis component to decide what to do. If there were potentially a large number of waveform configurations I'd do this to avoid having too many similarly named messages exposed in the component's api.

3) Create a new message subclasses for each of the waveform configurations. The subclass can derive from Message or from WaveformMsg. This provides the most type safety, but creating new classes for every possibly message can become cumbersome.

I guess this is what people refer to as by-reference versus by-value?

Not quite. By-value, by-reference, and global objects refer to a high-level view of when copies of the class data are created. By-value objects create a new instance (copy) of the data when wires are branched. Most LV data types are by-value.

By-reference objects don't create data copies when the wire is branched; each branch still refers to the same instance of data. Creating a new instance of that type requires calling the type's Create method again. Unnamed queues are a native LV type that is by-ref. Note that both by-val and by-ref require wires are passed around.

Global is similar to by-ref, except calling the constructor again doesn't create a new instance of the data, it gets a reference to the existing instance of the data. A named queue is an example of a global object. This is what you are considering.

I try to stay away from globally available objects. It tends to lead to nasty dependency issues with too much interlinking between components.

Link to comment

...If I'm understanding your problem correctly, you've discovered you now need to send information about the waveform along with the waveform data itself, but you can't because the queue is typed for waveforms...

I'm with Daklu here except that if you're using the waveform type this isn't quite true - you can add anything you want to the waveform as a waveform attribute (metadata) and LabVIEW still treats it as a waveform data type and the queue is still valid.

Mark

  • Like 1
Link to comment

Using 'traditional' LV techniques this was often accomplished by flattening the data to a variant and adding attributes for the meta-data. Your analysis component then reads the variant's attributes and acts according to what it finds.

I think most people would have used a cluster (usually with a typedef) and just unbundled. That would also work for the OP

Link to comment

you can add anything you want to the waveform as a waveform attribute (metadata)

Smart! :star::thumbup1: I think this solution will get the OP up and running quickest.

I think most people would have used a cluster (usually with a typedef) and just unbundled. That would also work for the OP

Typedefs (any custom type, really) create dependencies between components. Using the waveform attribute or variant messaging keeps each component from being dependent on other components. Instead, the components are dependent on the interface definition. (i.e. Tag the waveform with the attribute "accelerometer" to indicate it contains accelerometer data.) If the component is reusable, it's much easier and safer to update the interface later as new requirements arise this way.

Link to comment

I could have an FGV which the daq class writes to (number of channels, locations) and the modules read from. But I was wondering if there is a way I can get these modules access to the daq class data method readChannelPositions(). I think the problem I've come up against is that the modules don't have a reference to the class and I can't pass a wire in to them. I guess this is what people refer to as by-reference versus by-value?

My perspective (at the risk of repeating much of what Dave said) is this:

We want components to be as independent of one another as possible. We want a component to respond to data received on its external interface.

A component can receive data via a wire or via some sort of communication. If the component is running in its own thread and needs to respond to external data at any time then we need to implement some sort of communication (signaling). (Yes, we can also use by-reference objects to do this, but I think this obfuscates the code and makes it much more difficult to maintain, since it increases the coupling between classes. In most cases it is far better to define an external data interface for a component.)

If we choose to use signals we can send any type of data we want, but we should define the simplest external interface possible for a component (to make the intent clear and to minimize the need for changes by decreasing coupling)--and use an appropriate messaging paradigm. (Very important: The message content and the messaging paradigm can and should be independent of one another! We should be able to change one or the other as needed!) We can send any data by flattening it (to a variant or a string or XML) but the receiver needs to know the type in order to interpret the message. To achieve that we can use one type per message or, send the type as part of the message (can get complicated), or, if we are using objects, make the top-level type of a single message an abstract class and use dynamic dispatching, extending the abstract class for each actual message type as needed (which is the essence of the Command Pattern). (In this case a sender or a receiver needs to have access to the definitions precisely for the objects it sends and receives, but not for other messages. This is the essence of defining component interfaces.)

Paul

Link to comment

Typedefs (any custom type, really) create dependencies between components. Using the waveform attribute or variant messaging keeps each component from being dependent on other components. Instead, the components are dependent on the interface definition. (i.e. Tag the waveform with the attribute "accelerometer" to indicate it contains accelerometer data.) If the component is reusable, it's much easier and safer to update the interface later as new requirements arise this way.

This is "traditional" labview we are talking about. wink.gif If you are suggesting to never use typdefs (because it creates dependencies) then I will take you to task on that. But I think "most" traditional labview programmers would use this method without all the complexity and overhead of conversions to/from variants.

Link to comment

But I think "most" traditional labview programmers would use this method without all the complexity and overhead of conversions to/from variants.

You are absolutely correct. Most traditional LV programmers would use a typedef without a second thought. Of course, most traditional LV programmers create highly coupled applications that offer little opportunites for reuse and become increasingly rigid and fragile over the life of the application. However, since the OP mentioned creating "a modular data acquisition & analysis tool," I inferred that he is interested in avoiding those problems. :)

If you are suggesting to never use typdefs (because it creates dependencies) then I will take you to task on that.

As a general rule I think it is a bad idea to expose typedef clusters as part of a component's public interface. Not because they create dependencies, but because they do not provide sufficient safety in the face of changing requirements. If you can guarantee that all code that will ever depend on that cluster will be loaded into memory whenever the typedef is changed, then you're fine. Sometimes I'll use them as private members of a component library to facilitate passing data between member classes. On the other hand, as soon as your cluster is released as part of a reusable component's public interface, you can't make that guarantee and any changes to the cluster run the risk of breaking existing code.

If my component's public interface has data that can't be represented using LV's native data types, I'll create a class that encapsulates the data and provide appropriate accessors that *do* use native data types. It's safer, more flexible, and ultimately results in reuse code libraries that are easier to understand and use.

Now... tell me more about this "Task" place you're taking me to. It sounds fascinating... ;)

Link to comment

Of course, most traditional LV programmers create highly coupled applications that offer little opportunites for reuse and become increasingly rigid and fragile over the life of the application.

I think you've been fed the hype intravenously. biggrin.gif That is certainly the OOP mantra. But no evidence has ever been proffered to support this kind of statement (you have a link?).

As a general rule I think it is a bad idea to expose typedef clusters as part of a component's public interface. Not because they create dependencies, but because they do not provide sufficient safety in the face of changing requirements. If you can guarantee that all code that will ever depend on that cluster will be loaded into memory whenever the typedef is changed, then you're fine. Sometimes I'll use them as private members of a component library to facilitate passing data between member classes. On the other hand, as soon as your cluster is released as part of a reusable component's public interface, you can't make that guarantee and any changes to the cluster run the risk of breaking existing code.

If my component's public interface has data that can't be represented using LV's native data types, I'll create a class that encapsulates the data and provide appropriate accessors that *do* use native data types. It's safer, more flexible, and ultimately results in reuse code libraries that are easier to understand and use.

Now... tell me more about this "Task" place you're taking me to. It sounds fascinating... ;)

Sure. New thread? biggrin.gif Ya think people will talk wink.gif

I would start off with explaining why typdefs (in labview) provide single point maintenance and expand into why OOP breaks in-built labview features and why many of the OOP solutions are to problems of it's own making tongue.gif Maybe I should bring my old signature back lightbulb.gif

Edited by ShaunR
Link to comment

Hello there,

Wow, thank you all for the help and advice, I really appreciate it. I can now upload a few pictures to show you my implementation in case it is of interest. It is still very early days at the moment but it shows what I'm doing.

This is my main front panel with just a couple of child windows open for demonstration. From the menu bar the user can select to have multiple live views of the data (bar charts, fft etc) and they can also take a defined measurement - as in the fft window shown. These are child windows to the parent.

post-13935-0-35919700-1291705660_thumb.p

The DAQ loop sits on the block diagram of the main front panel. It grabs a bit of data from the active devices and sends it out to a notifier and lossy queue.

post-13935-0-69860000-1291705657_thumb.p

This is the implementation of a child window - live or measurement. The bottom loops sits there just grabbing data from the queue/notifier and sends a section of it to an xControl to be plotted/saved etc.

When the panel is closed the child windows fires off an event to report back its own reference and the window bounds so that the childWindowManager class knows where to open it next time.

post-13935-0-70464100-1291705656_thumb.p

It probably looks pretty basic to you guys but I'm really happy with it so far - I'm still learning quite fast but it is proving to be nice and modular - it is dead straight forward to add another analysis technique for example, and the complexity of the analysis can be contained nicely within the xControl.

The bit I was originally asking about was where in the child window I do the index array to extract just the elements that I am interested in (the accelerometer data for example) to send to the xControl. As this is happening ~5 times a second and there's a lot of data, and as I can assume that the active devices list will change only at startup I did not want to invest too much time in checking the attributes of the waveform array on each loop. I had initially thought about having a subvi which would be called to get the index and length of the part of the array that contains 'accelerometer' in the NI_ChannelName property for example, but it seems like a lot of work to do on each loop.

I had thought about splitting the waveform array into three queues - microphone data, accelerometer data etc but then there is the overhead in doing this and it means the daq class is then kind of assuming how the data should be split up - Order Tracking analysis will require the tacho and the accelerometer for example so that child window would then end up waiting on two queues and checking time stamps which would also be messy. I've left it to the child window to decide what data it needs for its own analysis.

At the moment I am thinking of using the 'on first call' boolean so that when the child window is first opened the first bit of data from the queue is taken and the attributes are searched to extract the start index and length for that sensor type and then these values are used in subsequent loops.

I think this is the way that I will go but that to me sounds like a method which should be a part of the daq class but is it bad practice to have a class method without the class wires going in & out? (I wouldn't need access to the class data so I don't need them).

Thank you again for the help. I will re-read your comments again at work to make sure I fully understand everything but I wanted to post these pictures before I get stuck behind my work firewall.

Regards,

Martin

Link to comment

Ahh, I missed a step - what I had wanted to do was call a daq class method in the child window when the window is first opened called 'getAccelerometerLocations' which would read the value from a class property. But that is what I don't think I can do because I can't call a class method from here which has access to the class data.

Because of this I'm left with the idea of calling a basic subvi which doesn't have access to the class data but is a part of the class - I kind of half way house of encapsulating the knowledge of how to extract channel property info within the daw class.

Link to comment

Ahh, I missed a step - what I had wanted to do was call a daq class method in the child window when the window is first opened called 'getAccelerometerLocations' which would read the value from a class property. But that is what I don't think I can do because I can't call a class method from here which has access to the class data.

Because of this I'm left with the idea of calling a basic subvi which doesn't have access to the class data but is a part of the class - I kind of half way house of encapsulating the knowledge of how to extract channel property info within the daw class.

OK, here's my opinion (and it's only an opinion :) )

First, there's nothing wrong with having a class method that just does some helper function that doesn't need direct access to the class data. If the method only makes sense to use in that class, make it private so you know not to try to use it elsewhere.

Second, don't worry too much about having a function that sorts the waveforms by attribute and using it on every call. If the size of the waveform array is relatively small (tens of waveforms) I don't think you'll ever notice the overhead. And this approach is robust - if on any sort you don't find what you're looking for, you might throw an error. Or the maybe the data doesn't get displayed - at least you know that your data is not what you expected. If the overhead cost seems high, then maybe use the on first call primitive, but my experience has been that using that tool is perilous because my interpretation of what should be first call doesn't always agree with the run-times interpretation.

Third, if you need the data from a specific instance of a class to operate (like the "getAccelerometerLocations" method) , then pass that class instance in and use an accessor to get the data. I presume you're launching the child window with an invoke node, so just set the class control value before you start the VI and you have the class instance to read. Or better yet, include the DAQ class instance in the class data for the display class, initialize the DAQ class instance, and then pass the display class instance in on startup of the display method. I don't think this creates any kind of unwanted class dependency because your display class doesn't have any utility unless there's an active DAQ class object. If a background scan class isn't running (your DAQ class), then there's no data being enqueued. Of course, if you go this route then you should use what I think jgcode suggested where you do the sort routine once when the DAQ channels are defined and then set these sort tables (data members of the DAQ class - I would use an array of typedef clusters (sorry, daklu). Each cluster contains the waveform name and an array of indices from the waveform array that contain data of that particular name. This approach can be completely dynamic as you can create as many of these sort tables as you need.). Then use that array in the display VI by searching for the desired name (this will be quick, since the array of clusters probably won't be more than single digits) and index into the waveform array using the sort table.

As always, YMMV, grain of salt, yadda, yadda,

Mark

Link to comment

Sure. New thread?

Done. Your serve. :D

This is my main front panel with just a couple of child windows open for demonstration...

I agree it looks good. And don't let the apparent simplicity discourage you from posting. If you're exploring modular programming and learning how to handle those issues, the last thing you want is a lot of complexity in each module.

it is dead straight forward to add another analysis technique for example, and the complexity of the analysis can be contained nicely within the xControl.

One point here... by putting the analysis algorithms in the xcontrol you are prevented from using those algorithms programmatically. You can't reuse the analysis in other apps unless you want to display it in exactly the same way, and there's no way for you to get the results of the analysis to do additional decision making. A more flexible approach is to create a lvlib or class that does the analysis and returns the results, then display the results on a control in your application code.

Xcontrols are best used to customize the way a user interface element responds to user actions. We have some legacy code in which a fairly complex parsing process was implemented as an xcontrol. Now we all look at it and say, "well that was silly."

Link to comment
  • 3 weeks later...

If we choose to use signals we can send any type of data we want, but we should define the simplest external interface possible for a component (to make the intent clear and to minimize the need for changes by decreasing coupling)--and use an appropriate messaging paradigm. (Very important: The message content and the messaging paradigm can and should be independent of one another! We should be able to change one or the other as needed!) We can send any data by flattening it (to a variant or a string or XML) but the receiver needs to know the type in order to interpret the message. To achieve that we can use one type per message or, send the type as part of the message (can get complicated), or, if we are using objects, make the top-level type of a single message an abstract class and use dynamic dispatching, extending the abstract class for each actual message type as needed (which is the essence of the Command Pattern). (In this case a sender or a receiver needs to have access to the definitions precisely for the objects it sends and receives, but not for other messages. This is the essence of defining component interfaces.)

Paul

I realize I'm chiming in on this conversation a bit late but I wanted to reiterate what Paul was saying. It is fairly easy to define a generic messaging architecture that can pass messages around your application and provide lots of useful features without regard to the messages themselves. As Paul stated, only the sender and the receiver need to know what is in the message and how to interpret it. Classes that are used for passing the messages can be generic such as an abstract class. By using this approach you have a standard interface for message handling that is flexible and reusable.

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.