Jump to content

Choices for shared data.


Recommended Posts

I am developing an application ("main-app") that communicates with an external device. I have defined an abstraction layer in the application for the device (using LV classes). Now I am using that abstraction layer to develop a simple simulator for the device.

When running against the simulator, I want to launch a separate VI .. call it "simulator-vi" .. to run in parallel with main-app, so that a tester can control the simulator. For example, the tester can press a button on simulator-vi to force an error condition to occur in the "device" in order to test the error-handling functionality of main-app.

What options are available to represent the simulator state so that it can be shared between main-app and simulator-vi? For my immediate purposes, I think the simulator state would be write-only for simulator-vi and read-only for main-app, but in the future it would make sense for main-app to be able to modify the simulator state too.

The simulator-vi will run in memory alongside the main-app, there is no need for it to run remotely.

I think I can use global variables to represent the simulator state, especially as long as it is read-only for main-app. But I'm interested in what other options I might consider, and pros and cons of the various possibilities.

Thanks,

Mark Zvilius

Link to comment

Grrr... I spent a couple hours responding last night only to have IE eat my post when I previewed it. frusty.gif

I am developing an application ("main-app") that communicates with an external device. I have defined an abstraction layer in the application for the device (using LV classes). Now I am using that abstraction layer to develop a simple simulator for the device.

You don't specify how you are using the abstraction layer, so I'll throw this out there in case you are doing something different.

Assuming your abstraction is an InstrumentController class, by far the easiest way to do what you want is to make an InstrumentSimulator class that is a child of the InstrumentController class. If your InstrumentController class includes a "Use Simulator" setting, back up and create a new class. Splitting up the functionality makes the code easier to understand and easier to test. InstrumentController is more reusable across different projects--if apps have different simulator requirements you're can create a project-specific InstrumentSimulator instead of modifying InstrumentController source code and potentially introducing bugs in other apps. Your main app needs almost no modifications to use the simulator. I use a conditional disable structure to switch between my real instrument drivers and my mock instrument drivers. No other changes needed.

What options are available to represent the simulator state so that it can be shared between main-app and simulator-vi? For my immediate purposes, I think the simulator state would be write-only for simulator-vi and read-only for main-app, but in the future it would make sense for main-app to be able to modify the simulator state too.

Is there some reason simulator.vi should be write only instead of read-write?

The first question you need to answer is if you want your simulator data to be a global data ("static" data in other languages) available to all simulator instances or by-ref data unique for each simulator object. It's easier to get something that runs using global class data. It's not easier (imo) to get something that runs robustly using global class data. By-ref class data offers more flexibility in how you use the class. For example, using by-ref class data you can simulate n instruments (each with their own simulator vi) by simply dropping n class constants on the block diagram. You can't do that with global class data. You can also make a by-ref class behave like a global class by wrapping the by-ref object in another class as a global variable. If you start with a global class you can't make it behave like a by-ref class. It will always be a global class. My default is to use a by-ref class, especially if there's a chance you might reuse the class in other projects.

If you decide you want a global class:

Global variables - Easy for class users to use--no need to drag class wires around. Easy for class users to misuse if they are not aware of its global nature. Easy to implement getters and setters. Requires potentially complex semaphores if you have any read-modify-write operations. Allows concurrent access to different data elements.

Named Single Element Queue - Built in protection against race conditions. No concurrent access unless you implement a separate queue for each data element. Very slight possiblity of stepping on an identically named queue.

Functional Global - You can make a functional global behave more like a global variable or more like a named single element queue, depending on how you implement it. Personally I don't see a need for them in OOP.

If you decide you want a by-ref class:

Data Value Reference - Build in protection against race conditions. No concurrent access unless you implement a separate DVR for each data element.

Unnamed Single Element Queue - Pretty much the same as the DVR, except you have to manually implement a bit more of the functionality. Since the introduction of DVRs in 2009, I find the DVR a more natural device for storing shared data than the SEQ.

When running against the simulator, I want to launch a separate VI .. call it "simulator-vi" .. to run in parallel with main-app, so that a tester can control the simulator. For example, the tester can press a button on simulator-vi to force an error condition to occur in the "device" in order to test the error-handling functionality of main-app.

If you have a separate InstrumentSimulator class, what you're looking for is a class in which the methods run in the client's execution branch, *except* for Simulator.vi, which runs in a private, parallel execution branch. SciWare created an Active Object framework for by-ref classes that shows how to launch a class member vi in a parallel branch. (See LaunchProcess.vi.) You'd do that as part of InstrumentSimulator's initialization routine. Assuming Simulator.vi is just setters and getters, you can simplify his design a lot by removing the command queue and message queue. You'd also replace his Process.vi with your Simulator.vi.

Making InstrumentSimulator a global class avoids all the complexities required with making sure both branches have the same DVR or unnamed queue refnum. The data sharing is built into the global variable so you just have to launch Simulator.vi when the app starts.

  • Like 1
Link to comment

First of all, thanks for the extensive reply. There is a lot there for me to chew on.

Assuming your abstraction is an InstrumentController class, by far the easiest way to do what you want is to make an InstrumentSimulator class that is a child of the InstrumentController class.

My abstraction layer is defined by a class, let's call it "Device".

There are two classes that inherit from Device: ActualDevice and MockDevice.

ActualDevice performs communication with the actual device.

MockDevice is the simulator.

I think this is basically in line with your suggestion, if I understand you correctly.

What I'm looking to do is have some of the methods in MockDevice reference some data that is shared with the parallel, "simulator-vi", so that a tester can manually control some device characteristics.

Is there some reason simulator.vi should be write only instead of read-write?

No. I just haven't seen the need for it to read the shared data yet.

The first question you need to answer is if you want your simulator data to be a global data

Very good point. The application I'm developing at present only talks to one device. But doubtless the next step will be talk to multiple devices, and having multiple MockDevice instances running (with a separate simulator-vi front panel for each) will be useful in the long term.

That brings up the question of how to launch multiple instances of a VI, so that I have multiple copies of the same front panel on the screen accessible to the user?

The terms "by-ref class" (aot "global class") and "data value reference" are new to me. I'll have to do some studying...

Thanks for your help,

Mark Z.

Link to comment

My abstraction layer is defined by a class, let's call it "Device". There are two classes that inherit from Device: ActualDevice and MockDevice. ActualDevice performs communication with the actual device. MockDevice is the simulator.

I think this is basically in line with your suggestion, if I understand you correctly.

Yep, your hierarchy will behave the same way as what I suggested.

The terms "by-ref class" (aot "global class") and "data value reference" are new to me. I'll have to do some studying...

[i'm not talking down to you, simply trying to be clear.]

You won't find anything in Labview's help about a "by-ref class." In general there are three kinds of data a class can have: By-value data, by-reference data, and global data. When I said "by-ref class" I meant a class in which all the user data is stored by reference instead of by value or globally. The differences between the types of data are:

By-Value Data: Branching a wire creates a copy of the data. No data is shared.

By-Reference Data: Branching a wire does not create a copy of the data. You can share data by simply branching the wire and using the class methods on both branches. Each class constant does create a new copy of the data.

Global Data: All branches and block diagram constants refer to the same copy of the data. Data is always shared.

Search the example finder for ReferenceObject.lvproj. That shows how to implement a by-ref class using a queue.

DVRs are in the help file. Are you using LV 2009?

What I'm looking to do is have some of the methods in MockDevice reference some data that is shared with the parallel, "simulator-vi", so that a tester can manually control some device characteristics.

Right. Build MockDevice as if it were a regular class, except instead of putting the state data (strings, booleans, etc.) directly in the class ctl, put them in a by-ref container. (Put them in a cluster and put the cluster in a DVR or on a queue as shown in ReferenceObject.lvproj.) Now in MockDevice:Initialize you'll have to dynamically launch Simulator.vi in a parallel thread. That's when you use SciWare's ActiveObject:LaunchProcess.vi as an example.

That brings up the question of how to launch multiple instances of a VI, so that I have multiple copies of the same front panel on the screen accessible to the user?

I haven't needed to do that, but lots of others have. You can do that by opening Simulator.vi with option "8" wired into the Open VI Reference prim. This is my variation of SciWare's LaunchProcess implementation. (You can see I have the "8" wrapped up in a diagram disable structure.)

You'll notice that the class wire branches right after the Bundle By Name prim with one branch heading to the output terminal and the other branch being sent to the class control input in my ProcessLoop.vi. (That would be your Simulator.vi.) Because the ActiveObject class ctl has by-ref data, both branches refer to the same set of data.

post-7603-044725800 1277705668_thumb.png

Link to comment

Thanks again for the reply.

Yesterday I found a reference to "ReferenceObject.lvproj" in a FAQ on the NI site, so it's good to know I'm proceeding down the correct path. Your explanation of the meanings of "by-reference," "by-value," and "global" in the context of LabView is useful. While I understand the concepts from other programming environments, I am still working to grok the implications in the context of LabView. So thanks for that.

Yes, I am using LV 2009.

The LaunchProcess example is very useful. Thanks.

Now I have to go off and "do" for a while. If I run into any further roadblocks I'll return to this string. Thanks for the assistance and advice!

Link to comment

Your explanation of the meanings of "by-reference," "by-value," and "global" in the context of LabView is useful. While I understand the concepts from other programming environments, I am still working to grok the implications in the context of LabView. So thanks for that.

I was in the same boat. It took me some time to get my head around how programming concepts apply to Labview. In many cases I'm still learning...

Now I have to go off and "do" for a while. If I run into any further roadblocks I'll return to this string. Thanks for the assistance and advice!

Happy to help. I'll actually be offline for the first part of July, but if you start a new thread someone will speak up and help you out.

Link to comment

"start a new thread..."

What's the best practice (assuming I have a follow-up question in a week or two)? Reply to this thread or start a new thread? I would've thought the former.

Depends on how closely related it is to your original topic. If you wanted to ask questions on how to implement by-ref data using a DVR, I'd recommend a new post with a more accurate title for your new question. New posts tend to get a little more attention than older posts. When spawning a new thread that's an offshoot of a previous thread it's a good idea to link back to the original to give readers an easy way to get the history. On the other hand, if you still had questions about the consequences of different choices for sharing data that should be posted here.

This is a pretty casual community--about the only thing that will evoke a flame from others is if you spam the same question across multiple LAVA forums. (Even then the flame is more of a smoldering matchstick than a blow torch.)

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
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.