Something you hear about a lot on the LabVIEW users forum are people who want to stop a VI and then restart it. They will often say things like,
“I have a VI that acquires some data and displays it to the user just the way I want. Now I want to run it several times, but change the test setup between runs – and my customer wants it all done automatically. I have code that can do the test setup, but now I need to run my acquisition and display VI and I can’t figure out how to programmatically click the ‘Abort’ and ‘Run’ buttons.”
Clearly, they can’t actually click the button, but if you consider the situation for a moment you realize that they don’t actually need to start and stop any VIs to obtain the desired behavior. All you really need is a way to put VIs into a mode where they don’t do anything until you are ready for them to continue. It’s, likewise, not unusual to hear from people who are trying to sort out how to add interactivity to a state-machine the normally runs without human intervention. Of course this is why the whole question is interesting: We all run into situations where we want the code to stop and wait until something happens.
Obviously, if you put the problem that way its easy to see that we have just described an event-driven application – which we have just spent several weeks building.
A Little Review
However, to fully realize our goal of implementing “stop-start” behavior, we need to give further consideration to the timeout events we have been using. If a VI is truly going to be dormant when we “turn it off” and then go back to normal when “turned on” we need to first have the code that defines the VI’s “normal” operation (whether it be sequential logic, a state-machine, or something in between) located in the timeout case. Then second, we need to be able to disable and then re-enable the timeout event.
Already in the testbed code we have seen that you can use a negative timeout to turn off the timeout event – as we do in some of the logic for the error checking initialization code. The thing is, this technique has a lot more to offer than as simply a way to trap startup errors. As an example of what else it can do, we are going to expand our testbed application such that instead of simply reading data from one process, it will be capable of drawing data from three different processes. One will return random numbers, one a sine wave and one a ramp signal. To select the input to read, we’ll have a pop-up menu on the display process’s front panel. Although this would seem to be a pretty significant change, you’ll see that it’s really not.
Modifying The DAQ Process
The first thing to note when considering the changes we will need to make, is that the basic infrastructure for this new capability already exists. We already have a structure where we are dynamically changing the timeout event’s timeout.
- When it the acquisition process starts, the timeout is set to zero.
- If initialization is successful, the timeout is set to 1 so acquisition is started.
- Each time a new datapoint is acquired and sent to the GUI, the timeout is set to the value read from a FGV.
All we have to do is expand on this existing functionality, and the first step in that process is to change the behavior in Item 2 above. Currently, the code wants to start acquiring data as soon as it can, so when the error check finishes, the timeout is set to 1. We want to change that behavior because we don’t want the process to start acquiring data until it’s turned on. To implement that change requires a single key-stroke to change the “1” to a “-1”.
Now that the code is set to wait until it is enabled, we need a way to turn it on – and by extension turn off all the processes that are not being enabled. Hopefully, you are thinking ahead and already see where I am going with this: another UDE. Let’s call it something logical like
Change Source, and make the data it carries a string named
Source Name. Normally, you might think that this would be a place to use something like an enumeration, and normally you would be right. However, there are exceptions to all rules, and as you will see this is an interesting exception.
With the new UDE created, here is its event handler…
…and as you can see it couldn’t be simpler. We compare the
Source Name value to the contents of a string control called
My Name and if they match, the timeout is set to 1, otherwise it’s set to -1. Because all three data sources will incorporate this same logic, the effect is that only one of the three – the one whose name matches the
Source Name value – will be enabled.
But where does
My Name come from? I added this control to the front panel and connected it to a terminal on the connector pane. We will need to modify the startup code to populate this just before running the process VI – and that’s our next stop.
Oh, one more thing: Now that we have the acquisition process modifications done,(yes, that is all there is to it…) I made two copies of it changed the data generation code in the copies to produce a ramp and a sine wave, and added the new acquisition VIs to the project.
Passing a Startup Value
To this point, the process VIs haven’t needed any information when they were launched, but now the acquisition processes at least need to know in essence “who they are”. Here is the modified
The VI reference to the
Open VI Reference node now shows the added string input terminal, which also appears on the
Start Asynchronous Call node. To that new input, I have wired the
Label value from the launch process data. Remember that, it will become important in just a moment.
The only other modification required is to add a VI, after the launch loop, that broadcasts a signal to the system telling it that the launch process is finished. Let’s take a quick look at why that signalling is needed and how it works.
During startup it can be critical for things to happen in the right order. Most of the time you can ensure proper sequencing by simply launching processes in the right order. Sometimes, however, conflicting requirements can leave you with no “right order”. As you might imagine, there are many possible scenarios requiring this sort of synchronization, and just as many potential solutions from which to choose. Moreover, as with Boolean logic, the “correct” solution will sometimes depend on how you are thinking about the problem.
In our testbed, we see a common situation: I like to have the display process launch very early in the startup process. In fact, the only thing that will typically launch before the GUI is the exception handler – which always comes first because the error handling has to be in place before you do anything. The GUI comes next because it is often the logical place to initialize common system resources that the acquisition processes will need when they start. The conflict lies in the need for the GUI to initialize its own front panel. Now that there are three acquisition processes running, the GUI had to select which one to display first. However, before one can be selected, the process VI has to be running and it won’t launch until after the GUI.
The solution to this conundrum is another type of “stop-start” situation. We need to let the GUI initialize everything except its front panel and then make it pause and wait until it receives a signal telling it that the launcher is done. Once that signal is received, the GUI can finish its initialization and commence normal operation.
To implement this functionality in a generic, reusable way, I have created two simple VIs that encapsulate a named LabVIEW notifier. Here is the routine that waits for the notification:
The VI is contained in a library that creates a name space for the logic. The VI first acquires a reference to a notification, the name of which is derived from the name of the library containing it &ndash: in this case
Launch Completed. It then waits for that notification to occur, but with a timeout specified. To make this code easier to use, I modified the API slightly to allow the developer to define the timeout in units of time. Finally, if the wait ends in a timeout, I generate an error to that effect.
The VI for sending the notification is equally simple:
Located in the same library as the wait VI, the sender also derives the notification name from the library name and then sends the notification. An important point to remember is that notifiers are often likened to queues because on the surface they seem very similar. However, in addition to the fact that a new notification will overwrite an old one, there is another big difference. If you have multiple listeners monitoring the same queue, LabVIEW channels the enqueued data to the various VIs in round-robin fashion. For example, say you have three VIs all pulling data from the same queue, any one VI will only see every third item enqueued. By contrast, a notifier works more like an event in that if you have three VIs waiting for notifications the same notifier, they will all see every notification.
Adding Control to the GUI
So to recap, we have modified our existing acquisition process, created two new ones using it as a template, and modified the startup logic to pass a new piece of data that the acquisition processes need. The only thing left now is to update the GUI.
Start on the front panel (always a good place to start), add a test ring control and label it
Data Source. But a text ring in the current state isn’t very useful because it doesn’t contain anything. To fix that, we will (using a property node) insert into the control an array of strings that we want to appear as selections. And where do we get the array of strings? Here’s the code:
Using data from the same VI that the launcher used to obtain a list of startup processes, we generate an array of all the process labels, delete the first two elements (as discussed earlier, the exception handler and display processes) and write the remainder of the array to the ring control’s
Strings property. The result is a popup menu containing the names of the acquisition data sources.
Next, we add an event case for a value change event on the ring control.
The ring value is a numeric, so the event handler uses it to index an element from the array of process labels, and uses the resulting string to fire the
Change Source event. This is why we can use a string as the event data: Both the string we are using the fire the event, and the strings we are using to tell the processes who they are, derive from the same source: the process labels. Consequently, it is impossible for them to not match.
Finally, we need to look at the GUI initialization logic.
The first subVI called is the wait routine discussed above. When that notification is received, all the processes are loaded so the GUI can finish initializing itself by firing two value change events: one to set the sample period and a second to select the initial data source.
As usual, the completed code is below and when you run it you will now see that you can select between the three data sources we discussed. However, there is another lesson to be learned. I started this discussion saying that one might feel justified in saying that it would be a major undertaking to make such fundamental changes in the way an application operates. But we have just seen that it really is not. I can honestly say that describing what I did took much longer than making the modifications – and the reason is simple. I didn’t have to write very much code. Instead I was able to leverage features that were already built into the code, and reuse ideas that had already served me well in the past.
The sort of maintainability that allows our code to be nimble and not brittle should be a goal of all that you do, but it takes practice. Practice, and an understanding that learning never stops because there is always more to learn about the craft that we practice.
Next time, we are going to take this line of work a step further and consider a more complex case. Sometimes you don’t want the data sources to stop acquiring data when they are “inactive”. Rather you want them to continue monitoring their respective inputs even when they are running in the background. But, how do you then manage the data transfer to the GUI? Well, perhaps the best solution for effectively displaying process data on the GUI is to never pass process data to the GUI. Sound confusing?
Until Next Time…
One quick “PS”. Now that I am getting a good number of these posts done, I have been thinking I would like to have them available in languages other than English. I’m not exactly sure how it would work yet, but if you would like to help with the effort, please contact me.