Expanding Data Processing Bandwidth — Automatically

Well-written software can typically deal with any performance requirement pretty easily as long as the requirement is constant. It’s when requirements change over time that things can get dicey. For example, if your test system generates a new data packet to process every second and it consistently takes 5 seconds for the data to be processed, a little simple math will tell you how much processing bandwidth you need to create to keep up with the flow of data. But how are you to properly size things when variability is inserted into the process? What if the time between data packets can vary between 100 msec and several minutes? Or what if the data processing time can change dramatically due to things like network traffic?

These are the kind of situations where the processing needs to be more than simply “flexible”, it has to be able to automatically maintain its own operation and reconfigure itself on the fly. To demonstrate one possible implementation of this “advanced” technique, we will build on the simple pieces that we have learned in the past. In some ways, good software design techniques are like Lego blocks. Each one by itself is not very impressive, but when you stick them together, magic happens. But before we can stick anything together, we need to understand…

…what we’re going to do.

You’ll notice that any of the bandwidth management challenges that I mentioned earlier can be addressed by either adding more data processing clones, or removing existing ones that are being underutilized. Consequently, the question of how to implement this self-maintenance functionality really gets down to a matter of how to dynamically manage the number of data processing clones that are currently available — which in turn boils down to answering two very simple questions:

1. How do we know we need more?

Given that the whole point of the exercise is to manage a queue, the current state of that queue will give us all the information that we need to answer this question. Specifically, we can know when more processing bandwidth is needed by monitoring how many items are currently in the queue waiting processing. When the code starts to see the depth going steadily up, it can launch additional processes to handle the data backlog. Of course, this functionality assumes that there is a process that is constantly monitoring the queue and managing that aspect of its operation — which we actually have already in the test code from last week (Data Processing Queue Handler.vi). All we have to do is repurpose this VI to be a permanent part of the final application.

2. How do we know a clone is no longer needed?

The one part of the system that knows whether a clone is being under-utilized is, in fact, the clone itself. As a part of its normal operation, it knows and can keep track of how often is it being used. Having said that, there are (at least) a couple of ways to quantify how much a clone is being utilized. We could, for instance, consider how much time the clone is spending processing data versus how much time it spends waiting to receive data to process. If the utilization percentage drops below a given limit, the clone could then shut itself down. However, for this demonstration, I’m going to use a much simpler criteria that, quite frankly, works pretty well. The code will simply keep track of how many times in a row it goes to the queue and doesn’t find any data.

Code Modifications

Before I start describing the changes that will we will need to make in order to fashion this new ability, I want to consider for a moment the thing that won’t have to change: Queue Test.vi. You might be tempted to say, “Well big deal. All it does is stuff some data into the queue every so often. Who cares if it doesn’t have to change? It’s not even deliverable code”

While that is undoubtedly true, the fact of the matter is that this test routine is important, but not because of what it is or what it does. Rather we care about Queue Test.vi because in our little test environment, it represents the rest of our application — or at least that part of it that is generating data. Consequently, the fact that it doesn’t need modification means that your main application, likewise, won’t need modification if you decide to upgrade from a data processing environment that uses a fixed number of data processors to one that dynamically manages itself.

Data Processing Queue Handler.vi

First, note that previously this routine’s primary job was to simply report how deep the data queue was — a bit of functionality that would likely have not been needed in a real application. Now however, this routine is going to be taking an active part in the process, so I started the modifications by adding the error reporting VI that will transfer errors it generates to the exception handler.

New Queue Handler Timeout Case

In addition, because the software will initially only start a single data processing clone, I also modified the timeout event handler that performs the VI’s initialization, by removing the loop around the clone launching subVI.

The remainder of the modifications to this routine occurs in the event handler for the Check Queue Size UDE. Previously this event only reported how deep the queue was getting. While it still performs that function, that queue depth information in addition now drives the logic that determines whether we have enough processing bandwidth online.

New Queue Handler Queue Size Check Case

Note that the queue depth is compared to a new configuration value called Max Queue Size that defines how large the queue can grow before a new data processing clone is launched. Regardless of whether it launches a new clone, event handler calls another new subVI that returns the number clones that are currently running. As you will see in a moment, one of the modifications to the data processing VI is the addition of logic that keeps track of the names of the clones that are running. The subVI that we are calling here returns a count of the number of names that have been recorded so far.

Data Processor.vi

Turning now the data processing code itself, the first stop is in the state-machine’s Initialize state. Here we have all the same logic that existed before, but with a couple minor additions

New Data Processor Initialize

First, there is a new subVI that registers a clone is starting up. This subVI writes the clone’s name to the FGV that is maintaining the clone count. Second, there is also a new shift register carrying a cluster of internal data that clone will need to do its work. All that is needed during initialization is to set a timestamp value. The Check for Data state is next and it has likewise seen some tweaks — the most significant of which is moving the logic for responding to the dequeue operation into a subVI.

New Data Processor Check for Data

The justification for this move lies in the fact that this logic is now also responsible for determining whether or not the clone is being adequately utilized. As I stated before, each time the clone goes to the queue and comes up empty, the logic will increment a counter that is being carried in the new shift register’s data. If this count exceeds a new configuration value Clone Idle Count, the code will branch to a new state that will shutdown the clone. Likewise, anytime the clone does get data to process, it will reset the count to 0. The changes to the Process Data state, which comes next, are pretty trivial.

New Data Processor Process Data

All that happens here is that the timestamp extracted from the data to be “analyzed” updates the new cluster data — as well as the indicator on the front panel. Finally, there is the new state: Self Shutdown

New Data Processor Self Shutdown

…which simply calls a subVI that removes the clone’s name from FGV maintaining a list of all running clones, and stops the event loop.

Let’s talk about “Race Conditions”

All we have left to do now is test this work and see the differences that it makes, but before we can do that, we need to have a short conversation about race conditions. Very often developers and instructors (myself included) will talk about the necessity of avoiding race conditions. The dirty little secret is that as long as you have multiple things happening in parallel, race conditions will always be present. The real point that these admonitions attempt to make is that you should avoid the race conditions that are unrecognized and potentially problematic.

I bring this point up because as you do the following testing, you may get the chance to see this concept in action. The way it will appear is that the system will launch a new clone immediately after one kills itself off for being underutilized. The reason for this apparent logical lapse is that a race condition exists between the part of the code that is checking to see if another clone is needed and the several places where the clones are deciding whether or not they are being used. There are two causes for this race condition, one we can ameliorate a bit and one over which we have no possible control.

Starting with the cause we can’t control, a simple immutable law of nature is that no matter how sophisticated our logic or algorithms might be, they can not see so much as a nanosecond into the future. Consequently, the first source of a race condition is that when the queue checking logic sees that there are three elements enqueued, it has no way of knowing that a currently active clone will be available in a few milliseconds. While it is true that under certain circumstances it might be possible to provide this logic with a bit of “foresight”, there is no generalized solution to this aspect of the problem. Consequently, this is an issue that we may just have to live with.

The news, however, is better for the second cause. Here the problem is that with all the clones having the same timeout between data checks, it is probable that sooner or later one of the clones is going to become “synchronized” with the others such that it is always checking the queue just after it was emptied by one of the others. However the solution to this problem lies in its very definition. The cure is to see to it that the clones do not have constant timeouts from one check to the next. To implement this concept in our test code I modified the routine that returns the delay to add a small random difference that changes each time it’s called.

The bottom line is that while completely removing all race conditions is not possible, they can be managed such that their impacts are minimized.

New Tests for New Code

The testing of the modified code starts the same as it did before: open and launch Data Processing Queue Handler.vi and Queue Test.vi. The first difference that you will notice is that only 1 clone is initially launched, but at the default data rate, 1 clone is more than adequate.

Now decrease the delay between data packets to 2 seconds. Here the queue depth will bounce around a bit but the clone count will stabilize at 3 or 4.

Finally, take the delay all the way down to 1 second. Initially the clone count may shoot up to 8 or 9, but on my system the clone count eventually settled down to 6 or 7.

At this point, you can begin increasing the delay again and the slowly the clones will start dropping out from disuse. Before you shutdown the test, however, you might want to set the delay back to 2 seconds and leave the code running while you go about whatever else you have to do today. It could be instructive to notice how other things you are doing on the same computer effect the queue operation. You might also want to rerun the test but start Queue Test.vi first and let it run for a minute or so before you startData Processing Queue Handler.vi — just to see what happens.

Further Enhancements?

So we have our basic scalable system completed, but are there things we could still do to improve its operation? Of course. For example, we know that due to timing issues which we can only partially control, the number of clones that is running at one time can vary a bit, even if the data rate is constant. One thing that could be done to improve efficiency would be to change the way clones are handled. For example, right now a data processor is either in memory and running or it is closed. One thing you could do is create a new state that a clone could be in — like loaded into memory, but inactive. You could implement this zombie state by setting the timeout to 0, thus effectively turning off the state machine.

It might also be helpful to change to queue depth limit at which a new clone is created by making it softer. Instead of launching a new clone anytime the queue depth exceeds 3, it might be useful in some situations to maintain a running average and only create a new clone if the average queue depth over the last N checks is greater than 3.

Who knows? Some of you might think of still other modifications and enhancements. The point is to experiment and see what works best for your specific application.

Parallel Data Processing — Release 2

The Big Tease

So what is in store for next time? Well in the past we have discussed how to dynamically launch and use VIs that run as separate processes. But what if the code you want to access dynamically like this happens to exist in a process that you have already compiled into a standalone application? If this application is working you don’t want to risk breaking something by modifying it. As it turns out there are ways to manage and reuse that code as well, even if it was created in an older version of LabVIEW. Next time we’ll start exploring how to do it.

Until Next Time…

Mike…

Expandable Data Processing

When creating an application a common approach to managing application computer bandwidth is to structurally isolate the portions of the application that are doing the data processing from those implementing the data acquisition. The goal of this segmentation is to prevent the operation of the one from impacting the speed of the other. But if we think about what we learned from the posts preceding this one, we can see that this use case is just a specialized case of a general principle. Namely, that allowing processes to run in parallel reduces the likelihood that their execution will impede the operation of their peers by improving the overall utilization of the computer’s resources. Consequently, it behooves us to consider how we can allow data processing to run in parallel with the rest of the application’s code — and so garner a few of the many advantages that this approach offers.

Parallel Data Processing

It first should be noted that we already have most of the tools and concepts that we will need to build this parallel data processing capability. In fact, one possible implementation is not so different from our general approach to building state machines. So with that idea in mind, let’s consider our perspective data processor’s high-level functional requirements.

Just the One?

First, since the idea is to improve efficiency by allowing more things to happen in parallel, we need to ask ourselves the obvious question, “Why stop at just one data processor?” This question is particularly urgent if you already know that your data processing takes longer than the time required to acquire another dataset. It is an appealing idea to have 2 or more data processors that can share the processing load. However, if you are going to be running multiple instances simultaneously, the process has to be reentrant. But that’s not a problem, we’ve done reentrant processes before and know how to make that work.

State-ly Expandability

Second, we need to consider the data processor’s internal structure. I have given this point away already by talking about state-machines, so let’s consider why this choice is a good one. At first glance it might seem like using a state-machine might be overkill for this application, after all you might figure that there is at most 2 states: Wait for Data and Process Data. This line of reasoning is valid, but it assumes that the data processing is a one-step process, so are there circumstances where this assumption isn’t valid?

If your processing is going to involve the complex analysis of potentially large datasets, you might not want to spend a lot of time processing flawed or invalid data. Hence before jumping right into the full-blown analysis it would be reasonable to have a state that does a quick sanity check on the data — and then another to handle the error that results if the data is bad. Next, consider what you are doing with the data when you are done with the analysis. There could be added limit checking of the results, and perhaps transfer of the results to a database or automated report generator — all tasks that could (and probably should) be expressed as separate states. Looking back now, by my count we are now up to 6 states — and we are still assuming that the analysis process itself is a single atomic operation that you will not want or need to breakup.

My point is that if you start with the basic outline of a state-machine and end up never having more than the two states, there’s no harm done because that basic structure is pretty lean. However if, as your project progresses, you discover that more states are needed, having that infrastructure already be in place can save a lot of time.

By the way, as a quick aside, I have often had the experience of having a customer request a significant change and then have them be amazed in how little time it takes to implement the modification. I have even had customers comment on how “lucky” I was that the code was structured in such a way that the change was even possible. Well let me tell you in the strongest terms possible that if luck was involved, it was luck of my own making. The better you design your code up front, the more “lucky” you will be when the time comes to make modifications.

The Right Kind of Communications

Third, we have to look at how we are going to communicate with these cloned state-machines. In past work on our testbed application, we have used UDEs and FGVs for communications between processes, and they might work here as well, but there would be problems. If you have one UDE or FGV feeding data to all the cloned data processors, you would have the problem of deciding which clone handles the each new update. While you don’t want to miss any updates, you certainly don’t want to process the same dataset twice either. The whole process would be fraught with opportunities for errors and race conditions. Of course you could solve that problem by creating a separate UDE or FGV for each clone, but then scalability goes down the tubes as the process(es) generating the data must keep track of how many clones there are any which one will get the next dataset.

Our way out of this conundrum is to properly apply a structure that people so often misuse: The Queue. This is the use case for which queues were born. The key feature that makes them so good in this sort of situation, is that if you have multiple receivers waiting to dequeue data from the same queue, LabVIEW guarantees that each new item inserted will only be seen by one of the receivers, and that enqueued data will be distributed to all the receivers on a first come, first served, basis. Moreover, because queues can be named, different processes running in the same instance of LabVIEW don’t need the queue reference to be sent to them. All they need to know is the name of the queue and they can get their own reference.

Yes, queues do still require polling, but at least there’s only one part of your code involved, and not the whole application. Moreover, there are two other mitigating factors present. First, in this sort of application the poll rate can often be much slower, on the order of once every few seconds. Second, because the state machine resides inside an event structure, it would take but a moment to implement logic that would allow the polling to be turned off altogether.

Let’s Look at the Test Code

As we start considering the code to implement this functionality, let’s first look at the test routines that we will use to evaluate the queue functionality. The VI Data Processing Queue Handler.vi has the responsibility of starting everything off by initializing the data processing queue and launching a predetermined number of data processing clones. This logic takes place in the VI’s Timeout event handler.

Queue Handler Timeout Case

You will notice that to simplify the process of obtaining a queue reference, I have encapsulated that logic in a set of subVIs for interacting with the queue. The other event of significance is the handler for the Check Queue Size UDE. While a test is running, we want to be able to monitor the number of items in the queue, but rather than simply polling the queue status, I created a UDE that flags the handler every time a new item is enqueued.

Queue Handler Queue Size Check Case

When the event fires, the handler calls the built-in queue function that returns, in addition to some other stuff that we don’t need, the number of items that are currently in the queue. Next, to feed data to the queue, I created a second test VI called Queue Test.vi. It’s whole job is to wait a delay period specified on the front panel, and then enqueue an item into the data processing queue.

Queue Test

You can see that at this point, the only value in the queue data is a timestamp, but the data is defined as a typedef. Remember! Any time you are creating a datatype that will be accessed by reference, whether it be a UDE, a queue, a notifier or an event, always make the data structure a typedef.

After the data value is enqueued, the code fires the event that tells the handler to check the queue size.

Introducing the Data Processor

Turning finally to the reentrant data processing state-machine (Data Processor.vi) we see that in addition to an event for stopping, the VI’s Timeout event handler includes the logic for three states, the first of which is Initialize.

Data Processor Initialize

This state’s job is to get the clone ready to start processing data. Consequently, it initializes the shift register holding the queue reference, and a second shift-register carrying a boolean value that we will discuss in a moment. The state also sets the next state to be executed to Check for Data, and retains a timeout value of 0 ms so, assuming that there are no errors during initialization, the state machine will immediately start waiting for data to process. Note that the Initialize state also opens the VI’s front panel. You probably would not want this feature in deliverable code except as, perhaps, a debugging option. I have included it here to make it easier for you to see the code at work.

Data Processor Check for Data

The Check for Data state starts by calling the queue subVI that is responsible for dequeuing an item. Inside this subVI, the dequeuing function is given a timeout of 0 ms so if there is not any data immediately available, the call will terminate with the timeout flag asserted. This Boolean value is inverted and passed out of the subVI to indicate to the calling code whether there is any data that needs processing. If the Check for Data state logic finds this bit set, the code sets the next state for execution to Process Data and sets the timeout value of 0 ms. If the bit indicating that data is available is not set, the code retains Check for Data as the next state to execute but sets the timeout to a longer value (5 sec) read from the application’s INI file.

Before we go on to talk about the Process Data state, we need to have a quick conversation about the boolean shift register. Normally, when the standard Stop Application event fires, a VI wants to immediately stop what it’s doing and abort. However in one significant way, this is not a “normal” VI. In order to protect the data that has been acquired, this process should only stop if the queue driving it is empty. To create that functionality, the VI incorporates deferred shutdown logic in the form of this shift-register. Because the value is initialized to false the loop will, during normal operation, continue regardless of whether data is available or not. However, when a shutdown is requested, the event handler does not immediately stop the loop, but instead sets the shift-register value to true and branches to the Check for Data state with a 0 ms timeout. If the queue is empty, the process will end at that time. However, if the queue is not empty, the VI will continue toggling between the Check for Data and Process Data states until the queue contents are exhausted.

Data Processor Process Data

As you would expect, the Process Data state basically consists of processing the last data dequeued and branching back to the Check for Data state to look for more. However, given that the only data in our test queue is a timestamp, you have probably guessed that the actual data processing to be done isn’t very expansive — and you’re right. In fact, the “data processing” consists largely of a wait, the duration of which varies at random between 4 and 6 seconds.

Putting it to the Test

To test this code, open and run Data Processing Queue Handler.vi. You will immediately see the front panels of three data processing clones open. Move them so they aren’t overlapping each other or anything else.

Now open and run Queue Test.vi. After a few seconds it will enqueue an item and then enqueue a new one every 6 seconds. Note that as each clone handles an item it will display that item’s timestamp on its front panel. Note also that the indicated queue depth never exceeds 1.

Now change the delay on Queue Test.vi from 6 sec to 2 sec. You will notice that the queue depth chart is now updating faster. Likewise, the queue depth will begin to show momentary increases to a depth of two, but the chart will always drop back to 1. In other words, there might be slight delays now and again, but for the most part three clones can keep up with the flow of data.

Finally, drop the delay to 1.5 seconds. With data coming at this rate, the queue depth will continue to go up and down, but now it is always going up more than it is going down. This overall upward trend shows us that we have reached the point where three clones are getting overwhelmed by amount of data that is being enqueued.

Queue Overrun

Now if you increase the delay back to 2 seconds, the queue depth will gradually begin to decrease as the slower flow of new data allows the clones to begin catching up on the backlog. Alternatively, if you just click the stop button, the two test VIs will stop and close immediately, but the clones will continue running until the queue empties out.

Parallel Data Processing — Release 1

The Big Tease

So we have learned the basics behind creating an environment for an application that supports an expandable data processing capability. For many applications this simple structure will be more than adequate, but (as we have seen) if the data starts coming too fast the queue can grow without limit. Of course this isn’t necessarily a problem if the periods of high data generation are interspersed with periods of comparative idleness. However this sort of variability can be a bit of a two-edged sword. The periods of low data throughput can give the system time to recover from a large backlog of data, but this variability can also make it difficult to estimate how many copies of the data processing process will be needed. Pick a number that is too high, and you’re wasting computer resources. Pick a number that is too low and you could still end up with a situation where too much data is being queued — perhaps to the point of running out of memory.

Well, the next time we get together we’ll look at how to modify the basic structure we have created thus far to add the ability for the software to decide on the fly when more clones are needed, and when to kill off existing ones that aren’t being used.

Until Next Time…

Mike…

Finishing the Configuration Management

For the last couple posts we have been looking at how to best utilize object-oriented programming methodologies. In that quest, we have taken as our example the goal of converting the parts of our testbed application that use stored configuration data to a configuration manager based on object classes. In this implementation, the classes represent the various types of potential data repositories.

Maximizing Flexibility by Leveraging Existing Code

When we stopped last time we had created all the basic code infrastructure and all we had to do was construct the dynamic dispatch methods that retrieve the data the application needs. In creating these methods, we have as our guiding principle reducing the amount of code we have to create by reusing as much code as we can. In other words we need to really spend some time thinking about how to structure our code such that it combines maximum reuse with maximum flexibility.

One good way of attaining that ideal is to allow for multiple levels of dynamic dispatch within the same method. Let’s say for the sake of argument that you have a method that will need to be accessible from 10 different subclasses. Furthermore, let’s say that 4 of those subclasses all need to do the same thing, an additional 4 are mostly the same but with a few differences, and the last 2 subclasses require fundamentally different logic to perform the same task. You could implement the logic that is common to the largest group of subclasses (the first 4) in the parent and let the remaining 6 override the parent to define their own solutions. This solution would work, but could potentially result in a lot of duplicated code. It depends on how similar the second group of 4 subclasses are to the first group of 4.

In creating the Initialize New method last week we saw a far better way to optimize the code: For the 4 subclasses that are similar, we could call the parent method in the child (thus taking advantage of that existing code) and then add a little logic to customize the functionality. This solution will work well in many situations, but one area where it will not is in scenarios where the common parts of the code need to pass data to the unique parts.

To address those situations, a very useful solution can be to write the parent method such that it has a subVI encapsulating the similar, but unique bits, that is itself a dynamic dispatch VI. By providing multiple levels of dynamic dispatch, you create a situation that is easy to understand and minimizes duplication of code. In our example, the 4 subclasses would use the parent implementations of the dynamic dispatch VI and subVI. The 4 subclasses that are similar, but a bit different would use the parent method, but override the dynamic dispatch subVI, and the two that are fundamentally different would override the parent method VI itself.

Getting Down to Business

So let’s start looking at what we need to do to finish our conversion — while remembering that most of this discussion will be about the reorganization and repurposing of existing code. For the most part, I won’t be going into how the basic functionality works since we discussed that code when it was originally introduced.

The basic internal structure of our four “blueprint” VIs will be largely similar. We will call the VI that creates the object we want, followed by a dynamic dispatch method that does what we need done. However, note that there isn’t (and shouldn’t be) a direct one-to-one correlation between the blueprint VI and the underlying method. We still need to be looking for ways to minimize redundant code. Back when we were setting up the testbed application originally we considered the need for unstructured configuration values — like you would usually store in an INI file. In fact, we implemented that capability and used it to store the default sample period. Consequently, one of the three methods we end up implementing will be used in a couple of different places now, and will be reusable in the future for other unstructured configuration data. So, let’s start with that one.

Get Misc Setups

Creating this method follows the same procedure as we have used before, except that this method has two input parameters and an output string. So the finished parent method has a front panel that looks like this:

misc read front panel

Another difference with this method is that if it is called for a subclass that does not override it, the parent VI does not provide any default functionality. To see what I mean, check out the VI’s block diagram. You see that it does nothing. This structure might seem rather pointless, but in reality it can at times be pretty handy. Say you have a method that is really only needed by one subclass. Without dynamic dispatch you would have to put a case structure around the code so the VI is only called in that one particular circumstance. However, with dynamic dispatch, this selection takes place automatically and without cluttering up the code with case structures.

With the parent VI (Read Misc Settings.vi) created and saved, we can now build the two subclass overrides. Starting with the Text version, to read the data from the INI file, we create an override VI in Config Data_Text.lvclass containing code that uses the built-in configuration VIs to fetch the data:

read misc - ini

But wait! This won’t work. You notice that the path to the INI file comes from the class data — which is fine except that we forgot to initialize it. I wanted to highlight this point because it is a very easy, and common mistake to make. Initializing the DVR is not the same as initializing the data in the DVR. All we have to do to fix this memory lapse is modify the class’ version of the Initialize New method, like so:

finish initializing the DVR - text

The subVI with the green banner builds the path to the application’s INI file programmatically. By the way, in case you’re wondering about whether the same issue applies to the other override to this method (the one for databases), the answer is “Yes”. However the solution there is a bit more complex, so we’ll deal with it in a moment.

The other thing to notice about the setup reading method’s code is that if the read doesn’t find the desired value in the INI file, it creates it and gives it a default value. Adding this extra bit is often helpful for recovering from situations where a uses has gotten in and mucked around with the INI file and deleted something they should not have. In any case, to see this code in action, we need to finish up the code in the Get Default Sample Period.vi “blueprint” VI:

get default sample period

You can now run this VI and get a valid result from the INI file. Of course, if you set the configuration data selector value in the INI file to Jet you will get a zero back. This results is because we haven’t provided an override for that subclass yet. Note that you do not get an error because not overriding a parent method is a perfectly valid thing to do.

Updating the Database Initialization

But we need the database data too, so let’s backtrack to the Initialize New VI in Config Data_DB.lvclass. The class data for this subclass is a string that the code will use in one way or another to connect to the database. However, there are three possible sources for this string:

  • Most ADO interfaces use a rather complex and specialized connection string that can identify logical names, network paths, security parameters and a lot more.
  • Although Jet does use ADO drivers, its connection string is much simpler. In fact, with the exception of the file path, it can be largely treated as a big string constant.
  • SQLite (which we aren’t going to support right now, but which exists in the class hierarchy) doesn’t use any ADO parameters. Keeping with its minimalist approach, all it needs is a file path.

So what are we going to do? Well, if you said, this sounds like a job for dynamic dispatch, you’re right! We need to create a method that will derive the correct string depending on which specific subclass is present. So let’s create a virtual folder and a subdirectory named _protected in Config Data_DB.lvclass with Protected access scope. Inside the virtual folder we will create a new VI using the dynamic dispatch template called Get Connection String.vi, and save it in the subdirectory. In addition to the signal IO the template provides, we need to add a string output:

get connection string

After saving this new the parent class method, we can fill in the overrides. However that code is pretty trivial, mostly scavenged from the original code, and has very little to do with OOP. Consequently, I won’t take time here to highlight it, but feel free to check it for yourself. Then with this work completed, we can finish the initialization code.

finish initializing the DVR - jet

Accessing the Database

Although we will be using a Jet database, note that the override for the Read Misc Settings method will reside in the Config Data_DB_ADO.lvclass subclass, not Config Data_DB_ADO_Jet.lvclass. The reason for this placement is that with the exception of the connection string (which we handle during initialization) the query logic is the same for Jet or most other ADO databases. By the way, what would we do if we ever did find a DBMS that needed something different? That’s right, we would just create a subclass for it and override the method in that new subclass.

In any case, most of the code we need now was lifted wholesale from the old configuration library:

read misc - jet

And with that we are done with the first of our blueprint VIs. However, this is also the procedure we will follow the VIs to read the error handling parameters, the startup processes and load the machine configurations for each state machine clone. Again, I won’t step through the creation of this code because from the standpoint of this post’s main topic (object oriented code development) there is nothing in these that is any different from the one we did go through. As a friend of mine used to say, “From here on, it’s just a matter of turnin’ the crank.”

Testing

To make the results of these changes easier to verify, I have made slight differences between the configuration in the INI file, and the configuration in the database. Notice that if you start the application with the INI file set to Text, the application launches with only two TC state machines running and the acquisition sample rate defaults to 500 msec. However, a setting of Jet produces the functionality we have seen before (3 state machines and a 1000 msec default sample rate).

Notice also that you have to make the INI file setting changes before starting the application. The logic could certainly have been written to make the settings reconfigurable on the fly, but it would have added another level of complication, so I will leave that as, “…as an exercise for the reader…”.

Before tagging this release of the code in SVN, I also went through the “recycled” code and made sure that the VIs were all saved in their proper locations and all the redundant code had been removed. A project window feature that made the latter task much easier was the ability to right-click on a folder and have LabVIEW list all the files with no callers. One thing to be careful about, however, is deleting “unused” files that are in classes. The logic behind this feature can’t identify VIs that are being dynamically linked — which pretty much describes dynamic dispatch VIs.

Finally, before closings out this post, I want to remind you of something important. I make no claim that this code is the best implementation for all possible situations. Although a lot of it is based on software that I developed for deliverable systems, the point on this blog is to provide you with examples, demonstrations, and models that you can then customize and mold to fit your customers’ specific needs. In music, there is the idea of “variations on a theme”. In fact, in a few cases the variations have become more famous than the original. So take the themes I offer here and feel free to deconstruct, rearrange and reassemble them into something that is new and exciting.

Testbed application Release 15
Toolbox Release 7

The Big Tease

What if I told you that LabVIEW incorporates a feature that can improve the quality of your code and reduce errors by helping to validate your math? Hey, this is a no-brainer! Everybody can use help validating their code. But, what would you say if I told you that most people never use this feature? Crazy…

Until Next Time…

Mike…

Objectifying the Testbed

Object-oriented programming as a technique promises a host of benefits, but suffers from the impression that it is in some way an “advanced” topic. In contrast, I feel that OOP is just a logical extension of the concepts that LabVIEW developers use every day. The basic problem has been with the way it has been taught. However, the various object-oriented frameworks that are overly complex and difficult to learn, haven’t helped matters. These bad “actors” often only serve to hide the inherent elegance of the OOP paradigm and scare off users that could benefit from it.

To help clear away some of the extraneous mystique, I have presented a brief introduction to the topic that provides a foundation sufficient to let us get into OOP by implementing a module for managing program configuration data that provides the calling application with a common interface regardless of how (or where) the data is stored.

Filling a Niche

Most of the work we will be doing will eventually replace the Configuration Management library. Now while this might sound like a major shift, it really is not because (like I repeatedly tell you) the whole point of good design is to make changes and upgrades like this possible. So let’s look at what this upgrade will need to do.

Normally a large part of any upgrade projects is defining the requirements, but due to the design work that was put in originally, we already have a pretty good handle on what the new class structure has to do. In terms of surface functionality, we know we have to be able to handle all the same information as before — with, of course, the ability to add more when we want or need that ability.

Designing the Structure

The trick is going to be sorting out what new functionality will be needed under the covers. At the most basic level we need to be able use either text files or databases to actually store the configuration data, so there we have two subclasses. But we need to consider whether each of those options needs to be broken down further.

On the “text file” side, the data might be coming from a standard INI file, or the code might be in using a custom text file format. Custom text configuration files are very common when some of the configuration data is tabular, since it is a pain to store tabular data in an INI file. However, regardless of the format of the contents, the basic mechanism for reading and writing text files remains the same so it probably won’t be valuable to have any subclasses under “text files”.

On the “database” side of things, however, the situation is very different. First of all, in terms of connectivity, you can access most databases through the standard ADO (ActiveX Data Objects, also sometimes called ole-db) interface. However, “most” is not the same as “all” and one common exception is SQLite. A popular, lightweight data management engine, SQLite can run on a variety of platforms — including some real-time systems. To keep its footprint small, SQLite utilizes a small custom DLL, rather than a large, but standardized, interface. So we need to make provisions for other types of connectivity by creating (for now) two subclasses below “database”: “ado” and “sqlite” — though we won’t be implementing the SQLite functionality right now.

Finally, what about “ado”? Can it be broken down further? Maybe. One of the advantages of ADO is that it, for the most part it does a pretty good job of hiding the differences between one database management system (or DBMS) and the next, but there are some variations it can’t paper over. These differences often relate to the version, or dialect, of SQL the DBMS speaks. However sometimes differences arise because some DBMS fundamentally don’t operate the same. For example, while most DBMS go to extraordinary lengths to hide exactly where and how the data is actually stored, Jet (the DBMS built into Windows) stores the data in a file you explicitly identify. Hence, while the connection to other DBMS might be defined in terms of network paths and logical names, with Jet you are connecting to a particular file.

To provide for these sorts of functional nuances, let’s create a subclass below “ado” for “jet” — understanding there could be others in the future.

Configuration Data Classes

This is what the hierarchy looks like so far, all drawn out.

Doing the Rough Framing

When you are building a house the first tradesmen to show up onsite are the carpenters to do the so-called “rough framing”. This process creates the skeletal form of the final house that is covered with rough exterior plywood. The idea is that later workers will fill in the details and fine tune the construction. And metaphorically speaking, that’s what we have to do now for our configuration data class.

For a class hierarchy, that framing consists of the directory structure and the class files themselves. Using the techniques I gave last time, I first create mirroring directory structures inside the project directory and the project itself…

Configuration Data Classes in Project

Note that I have also created some virtual folders inside the Config Data class which represents actual sub directories.

  • Interface
    The VIs in this folder will have public access scope. In fact they will be the only VIs in the library that are so scoped. Because these VIs are the only ones that outside callers will be able to call, they alone form the interface between the class hierarchy and the rest of the code.
  • _private
    As the folder title implies, the files that go in here will have private access scope. This assignment means that they will only be accessible from other VIs in the top-level class.
  • _protected
    This is another folder that specifies access scope for its contents. In the case of protected scope, the VIs in this folder will only be accessible from the top-level class, or any of its child classes.

Creating the Interface

With the functional scaffolding in place, we can start filling in the blanks. And the first thing we need to do is create what I referred to as the “blueprint” in the previous post — the VIs that the rest of the application will call. After going through the public VIs in the old library we see that there are really only 4 VIs that the rest of the application uses directly

  1. Get Default Sample Period.vi
  2. Get Error Handling Parameters.vi
  3. Get Processes to Launch.vi
  4. Load Machine Configuration.vi

Many of the others will still be used, but their presence will be hidden in subclasses. As I create these (for now) empty VIs, I make sure they have the same front panels and connector panes as the ones they will be replacing.

Adding Infrastructure

Getting back to our housebuilding analogy: After the framework is completed and the outside skin is on, the next job is to start installing some of the needed infrastructure, because without electric, water, sewer and perhaps gas connections, our new home is not much better than the cave dwellings that our prehistoric ancestors inhabited — and in some ways is far worse.

What we need to add to our nascent configuration management subsystem is some data handling, but not for our data. The data I’m talking is the private internal data that the subsystem needs to maintain in order to do its job.

Thinking about what our various bits of code need to do, we see first that there is certain data that will commonly be needed regardless of how you actually end up getting the data. What I’m thinking about here is the name of the operator and a password. Now, some subclasses might need both, while others might need only one or the other, and that’s fine. The important point is that if all subclasses could potentially need at least some of this data, it has to be data that is associated with the top-level class. However, this requirement for global availability causes a problem.

Remember how when we were defining terms in the previous post we said that in the LabVIEW world an object is a wire? LabVIEW wires, and data contained in them are by definition not global. If you want data from a wire you need to be connected to it. So how can we create wires that are separate, but which still share at least some of the same data? Well, one very good way of doing it would be to create one central data store that all the wires can access, and as it turns out LabVIEW incorporates a feature that is very efficient and so is perfect for such an implementation. I’m talking about the Data Value Reference, or DVR.

Our approach will be simple. We first create a DVR that is defined to hold the data we need and make a reference to that DVR the class data for our Config Data class. Then to access that data, we create a family of data access VIs to insert data into, or read data from, the DVR.

The first step on this process is to create another virtual folder in the top-level class named _dvr with privateaccess scope. Next, I create in that folder a typedef control named Config Mgr Data.ctl that consists of a cluster containing two strings, one for user name and one for password.

Config Mgr Data

When saving this control I create a subdirectory (called _dvr) to hold it. Likewise, I also create a VI in the same directory (and virtual folder) called Config Mgr DVR.vi that contains this code:

Config Mgr DVR

Two comments about this code. First, it has the basic form of a FGV where the variable value is the DVR reference. Because the case that generates the reference is only run once, this VI will always return a reference to the same DVR no matter how many times it is run. Second, the data for the DVR is the typedef we created. This point is critical. As with UDEs, if you define a DVR reference using a typedef, you can later change the typedef and it won’t break the reference.

To make this DVR available and inheritable through the class, I copy and paste the reference indicator into the class data cluster, like so.

Config Mgr DVR in Class Data

Then to provide access to the DVR contents, I create protected scope VIs that I store in a virtual folder and subdirectory both named _data access. Here is what the read VI for the user ID parameter looks like…

User ID Read

…and the write VI…

User ID Write

I next realize that the two main subclasses also have some data that will need to be held in common for their subclasses. So I repeat the process I just used to store a file path for the file subclass and the connection string that the db subclass needs. Here’s what the project looks like now.

project with basic infrastructure

Finally, before moving on we are going to need a way to initialize all the logic we have created, and to do that we will take our first foray into the exciting — though sometimes confusing — world of creating dynamic dispatch VIs. The goal is to create a method called Initialize New that causes the DVR in a new instance of a class to automatically initialize itself.

I start by right clicking on the _protected virtual folder in Config Data.lvclass and from the New sub menu selecting the option to create a new VI using the dynamic dispatch template. I leave the front panel of the resulting VI the way it is, but I change the connector pane, edit the icon and add this code to the block diagram.

Initialize top-level dvr

If the DVR in the class data is not valid, I call the DVR VI to get a valid reference and then use that reference to populate the class data. If the DVR reference is valid, the false case (not shown) does nothing. When I save this VI, I put it in a subdirectory named _protected.

To create the subclass versions of this method, I right-click on the subclass name and from the New sub menu select the item to create a new VI for override — which is the technical term for what we are doing. We are overriding the parent functionality with different functionality in the child.

However before we get a new VI, LabVIEW opens a dialog to ask us which parent method we want to override. After double clicking on Initialize New we get our new VI, also called Initialize New. After saving this new VI, (the default location LabVIEW picks is perfect) I modify the code to look like this:

initialize new - child

Most of this logic should be familiar because it is the same as we did for the parent. If the child DVR reference is not valid, we initialize it. Otherwise, we do nothing. But what is that funny looking VI in front of the initialization logic?

Object-oriented methodology recognizes that there are going to be times when a method in a child class will need to do what the parent method does, but perhaps a bit more or maybe do it a bit differently. One solution to this situation would be to simply duplicate all the parent code in the child. However that approach would be wasteful. What object-oriented logic does instead is it allows a child to directly call its parent’s version of the method. So here, the code first calls the parent’s version of the initialization VI and then executes the logic to initialize itself. In the end, both the parent and the child will get initialized.

Creating Objects

The time is now upon us to start tying this infrastructure together into an organized system. The first thing to sort out is how to create an object of a given type. One way is very similar to what you would do with a conventional datatype. If you wanted to create, say an I32 value, you would drop down a constant and start using it. In the same way, you can also drop down a class constant and wire to it, thus creating an object. The problem with this approach is that when LabVIEW instantiates a class it also loads into memory all the VIs associated with the class. What you can end up with is a situation where all the VIs for all the classes are loaded, even if you will never use some of the classes. The way to get around that problem is to load classes dynamically as you need them. This is the code I use to perform that operation.

create object dynamically

You will notice that the VI has no inputs, save the requisite error cluster. This is because the basic piece of information that specifies the specific class to be created will be loaded from the application INI file. But why the INI file? Isn’t the point of this exercise to get rid or configuration data in that file? Well yes, but there is a bit of a paradox at work here. Simply put, an application can’t go to a database it doesn’t know it has to find if it should look in the database to get its setup data. That basic piece of information has to be stored somewhere that will always be there, and on the Windows platform you have exactly two choices: the INI file and the Windows registry. Of the two, the INI file is much safer — you at least don’t have to worry about a user going wild and trashing their whole computer.

We are initially only going to support two options for managing configuration data, so we only need two settings (Text and Jet). The VI that communicates with the INI file reads one of these values from the Configuration Data key in the Data Management section and returns two values based on what it finds there: A relative path to the location of a class file, and the name of the file. Thanks to the naming convention we use, these two values are very closely related.

The remaining code loads the target class into memory and initializes it. In addition, the resulting object is buffered as in a FGV. For the details of how this process works, see the comments in the code. The final thing to notice is that the output from this VI is an indicator of the Config Data class datatype.

Building Out the Remaining Methods

All that’s left now is to implement the code that does what the testbed needs done, however this post is getting long and I have already passed on a lot of information for you to absorb — so we’ll leave that discussion (and the testing!) for next week.

Until Next Time…

Mike…

Objectifying LabVIEW

I suppose a good place to start this post is with an admission that, in a sense, it is flying a false flag. One way that you could reasonably interpret the title is that in this post I am going to be showing you how to start using objects in LabVIEW. That interpretation is not correct, and the troublesome word is “start”. The fact of the matter is that you can’t use LabVIEW without interacting with objects and many parts of it (think: VI Server) are overtly object-oriented — even without an obvious class structure. The language is built on an objects oriented foundation and so, in a very real way, has been object-oriented since Version 1.

What I am going to be showing you is how to simplify your work by building your own classes. As I stated in the teaser last time, the starting point for this discussion is the recommendation given in NI’s object-oriented training class that you should make your first attempts at using explicit object-oriented technique small, easy to manage subsystems — or put more simply, we need to start with baby steps.

Object-oriented baby steps

OK, so this is the point in the presentation where most presenters hauls out some standard theory, and moth-eaten descriptions of objects and classes — often lifted wholesale from a book on C++ programming. The problem with this approach is of course that we aren’t C++ programmers and the amount of useful information we can draw from an implementation of objects oriented programming that is so fundamentally flawed is minimal at best. The approach I intend to take instead focuses on key aspects of the technique that are of immediate, practical importance to someone who is working in LabVIEW and wants to take advantage of explicitly implementing object-oriented class structures.

A Quick Glossary

The first thing we need is a vocabulary that will let us talk about the topic at hand.

OOP Clouds

Now be forewarned that some of these definitions may not exactly match what you may read elsewhere, but they are correct for the LabVIEW development environment.

  • Class — An abstract datatype.
    If you think that sounds a lot like the definition of a cluster, you’re right! Due to the way LabVIEW implements object orientation, a class is essentially a very fancy cluster. In fact, when you create a class the first item that LabVIEW inserts into it is a typedef consisting of an empty cluster. Although you don’t have to put anything into the cluster, it provides a place to put data that is private to that class.
  • Object — An instance of a class.
    As with a normal cluster, every instance of a class has its own memory space. Consequently, a class wire is in most ways the same as any other wire in LabVIEW. We are still working in a dataflow environment.
  • Property — A piece of data that tells you something about the object.
    This is why there is a cluster at the heart of the class. You want to put in that cluster information that will describe the object is a way that is meaningful to you application. Because each instance of the class is a separate wire that has its own memory space, the data contained in the cluster describes that particular object.
  • Method — A VI that is associated with a particular class and which does something useful.
    So what do I mean by, “…something useful…”? Well that all depends on the class’ purpose. A the class that is responsible for creating a visual interface might have a method that causes an object to draw itself. While a class that manages the interface to data storage would likely have a method to store or retrieve application data.

From this simple list of words we can begin to see the general shape of the arena in which we will be playing. To recap: A class is a kind of wire. An object is a particular wire. A property is data carried in the wire that describes it in a useful way, and methods use the object data to do something you need done.

Dynamic Dispatch

Now that we have a basic vocabulary in place that lets us talk about this stuff, there are a couple of concepts that we need to discuss. I want to start with this exploration is with the mechanism that LabVIEW uses to call methods. Referred to as dynamic dispatch this feature it is often a source of confusion to developers getting started with object-oriented programming. A good way to come to grips with dynamic dispatch is to compare and contrast it to a feature of LabVIEW with which you may already be familiar: polymorphism.

Polymorphism (from the perspective of the developer using a polymorphic subVI) is the ability of a single functions to adapt to whatever datatype is wired to its inputs. For example, the low-level Add node in LabVIEW is polymorphic. Consequently, it can add scalar numeric if all types, as well as arrays of numerics of varied dimensions, clusters of numerics and even arrays of clusters of numerics.

Of course, from the perspective of the developer creating a polymorphic VI the view is much different. This flexibility doesn’t happen on its own. Rather, you have to create all the individual instance VIs that handle the various datatypes. For example, I often want to know if a value at a specific point in the code has changed from the last time this bit of code executed. So I created a polymorphic VI that performs this function. To create this subVI, I had to write variations of the same basic logic for about a half-dozen or so basic datatypes, as well as a version that used the variant datatype to catch everything else.

Dynamic dispatch (which is actually a form of polymorphism) works much the same way, but with a couple significant differences.

  • When the decision is made as to which instance VI is to be executed
    With conventional polymorphism, the decision of which instance VI to call happens as you wire in the subVI. In the case of my polymorphic subVI, as soon as I wire a U32 to the input, LabVIEW automatically selects the U32 version of the code. However, with dynamic dispatch, that decision gets put off until runtime with LabVIEW making the decision based on the datatype present on the wire as the subVI is called. Of course for that to work, you need a different kind of wire. Which brings us to the other point…

  • The criteria for choosing between VIs
    The wires that conventional polymorphism uses to select a VI all have one thing in common — they are all static datatypes. By that I mean that a wire is a U32, or a string or whatever and it can’t change on the fly. By contrast, with dynamic dispatch, the basis for selection is a wire that is an instance of a class, and the datatype of an object can be dynamic. However this variability is not infinite. A given class wire can’t hold just any object because class structure is also hierarchical.

Say you have a class named Geometric Shapes to Draw. You can define other classes (called subclasses) like Circle or Square that are interpreted by LabVIEW as being more specific instances of Geometric Shapes to Draw objects. Due to this hierarchical relationship, a given wire can be typed as a Geometric Shapes to Draw but at runtime really be carrying a Circle or Square. As a result, a dynamic dispatch VI can call different instance VIs based on the datatype at runtime.

However, one big thing that conventional polymorphism does have in common with dynamic dispatch, is that the power doesn’t come for free. You still have to write the method VIs for dynamic dispatch to call.

Inheritance

Remember a moment ago I referred to class datatypes as being hierarchical? The fancy computer science concept governing the use of hierarchical class structures is called inheritance. The point of this label is to drive home the idea that not only are subclasses logically related to the classes above them in the hierarchy, but these so-called child classes also have access to the properties and methods contained in their parent classes. In other words they can “inherit” or use data and capabilities that belong to their parents.

Handled properly, inheritance can significantly reduced the amount of code that you have to write. Handled poorly, inheritance can turn an otherwise promising project into a veritable train wreck. Which brings up our last point…

Proper Organization

Although organization isn’t really a feature of object-oriented programming, it is never the less critical. The simple fact of the matter is that while a disorganized, undisciplined developer might be able to get by when working in conventional LabVIEW, introducing the explicit use of classes can result in utter chaos. Of the real object-oriented failures that I have seen over the years, they all shared a lack of, or inconsistent, organization.

So what sort of organizational things am I talking about? Well it’s a lot of the same stuff that we have talked about before. For a more general discussion of the topic you can check out a post that I wrote very early on titled, Conventional Wisdom. What I want to do right now is highlight some of the points that are particularly important for object-oriented work.

The two main conventions (directory structure and file naming) go together because the point of one is to mirror the other. But rather than simply list some rules, I’ll demonstrate how this works. To start, I will create a directory that is named for the class hierarchy that I will build inside it. So if the point of this class hierarchy is, for example, to update my program’s user interface, I would call the directory something obvious like GUI Update. Inside this directory I would then create the top-level class with the file name GUI Update.lvclass. At this time I will also create a couple subdirectories (_subVIs and _typedefs) that I know I will undoubtedly be needing. Finally, I have learned over the years that being able to tightly control access to VIs is very important, so I will also create at this time a project library named GUI Update.lvlib and put into it the top-level class and a virtual folder called _subclasses with its access scope set to Private.

So the parent class is set up, but what about the subclasses? I simply repeat the pattern. Let’s say the GUI Update class has subclasses for three types of controls that it will need to update: Boolean, Digital and Cluster. I create subdirectories in the parent directory that are named for the subclass that will go into each, and hierarchically name the three subclasses GUI Update_Boolean.lvclass, GUI Update_Digital.lvclass, and GUI Update_Cluster.lvclass. I am also careful to remember to add the subclass files to the _subclasses virtual folder in the library, edit their icon overlays, and set their inheritance correctly — which is to say, identify their parents. Note that while the hierarchical naming structure doesn’t automatically establish correct inheritance, this convention does make it easier to visualize class relationships in the project file.

And so I go building each layer in my class hierarchy. With each new subclass I continue the same pattern so if I eventually want to find, say a subVI associated with the class GUI Update_Digital_Unsigned Word.lvclass, I know I will find it in the directory ../GUI Update/Digital/Unsigned Word/_subVIs.

Having a pattern to which you stick relentlessly — even one as simple as this one — will save you immeasurable amounts of time.

Creating the Blueprint

The next thing I do when creating a class hierarchy (but the last thing I want to talk about right now) is how the rest of the application will interface with my new GUI Update class. This is where the access scope we have been so careful to create comes into play. In the top-level class I always create a group of VIs that have their access scope set to public. These interface VIs form the totality of the external interface to the class hierarchy and so include the functions that define what the application as a whole needs GUI Update to do for it. The logical implications of this interface layer is why I sometimes call this step in the process, “Creating the Blueprint”.

In addition to providing a very clean interface, another advantage of having this “blueprint” is that if you ever need to expand your stable of subclasses, these interface VIs will serve as a list of functions you need to support in the new subclass — or at least a list of functions that you should consider implementing in the new subclass. To see what I mean, consider that the scenario we have been discussing is actually drawn from an application I created once. The list of public interface VIs was really very short: There was a method that read a value from a remote device and wrote it to the GUI object, one that looked for control value changes to write them to the remote device, and one that allowed the calling application to set control specific properties.

Of these, all GUI objects had to implement the first one because even the controls needed to be updated once a second. The reason for this constraint was that the remote device could also be reconfigured from a local interface and the LabVIEW application needed to keep itself up to date. However, the second interface method was only applicable to controls. Finally, the third interface method was implemented very rarely for the few subclasses that needed it.

What’s up next?

We have just about run out of space for this installment, but you may have noticed that something is missing from this post: Any actual LabVIEW code. Next time we will correct that sad situation by considering how to apply these principles to the creation of a class hierarchy that provides a common mechanism for storing and retrieving program data and setup parameters that works the same (from the application’s perspective at least) regardless of whether the program is interfacing with a database or text files.

Until Next Time…

Mike…

Command Line Arguments aren’t a relic of the past

Back when phones were something with wires attached to them, computer programs had nearly non-existent user interfaces. However, the need still existed to be able to pass data to programs. The standard way that developers used for accomplishing this vital task was through cryptic codes called command line arguments. These codes which were cryptic by necessity (a data entry line couldn’t be more than 80 characters), were simply appended to the name of the program the user was wanting to run.

Despite the advances that have come along over the years, operating systems still support command line arguments — and LabVIEW applications still sometimes have a need for them.

How they work

Even today, commands still get sent to operating systems in the form of strings, and curiously, the primary delimiter still used to separate the name of the program from other parameters is the space. This is why if you look at the shortcut for a program, the path to said program is almost always in double quotes: path and program names contain spaces.

The way the process works is that anything before the first space not inside double quotes is considered to be the name of a program and is sent to the part of the OS that is responsible for launching stuff. Everything after that first space is sent to the program that is being launched, which is then responsible for determining whether the remaining string contains valid information or trash.

When LabVIEW (or the LabVIEW runtime engine) parses the remaining text, the first thing it looks for is an initial delimiter in the form of two hyphens in a row, like this:

command line arguments

Anything after the hyphens are returned to the program through an application property as an array of strings. Note that the double hyphens have a space on either side.

But what’s it good for?

Unfortunately, while it may be easy to describe what this feature does, it can be tough to identify a good use case. To help us identify where this feature might be useful, let’s consider some of the technique’s major attributes.

  • It’s not very convenient
    Because the technique depends on creating what is essentially a custom command for launching your application, it can only be used within the context of a Windows shortcut — and did I mention that you have to create the shortcut manually?

  • It can still be pretty cryptic
    Although you have complete freedom in defining how the arguments are formatted and what they do, you still have a maximum line length constraining how verbose your complete command line can be.

  • It’s not secure
    The command line arguments are unprotected and can be changed by anyone with even a smidgen of technical savvy. Moreover, they can be bypassed completely if the user simply chooses to launch the program by double-clicking the application itself.

  • It’s error-prone
    There is no way to error check arguments before they get passed to the application. Consequently, the application needs to be very careful and validate all inputs.

Doesn’t sound too promising does it? Clearly command line arguments not suitable for anything critical, so the application needs to run just as well if the arguments aren’t there. Likewise, you usually don’t want to have users needing to muck around with them. Well, believe it or not there is at least one bit of functionality that falls well within this technique’s capability. Check it out.

Tracking execution

A common problem can arise when you deploy your application as an executable and it doesn’t work on the customer’s computer. Although, LabVIEW provides facilities for remote debugging that are really nice, it sometimes isn’t usable. First of all, to use it requires you to make a special build of your application that includes the remote debug capability. Second, this debugging technique assumes a level of network access that might not be available. Third, because you don’t want to sit for hours connected to a customer’s computer waiting for something bad to happen, it is only useful for problems that you can quickly and easily duplicate. However many problems are difficult to recreate on command.

One of the earliest software troubleshooting techniques can be particularly useful in isolating this type of intermittent error. The technique involved inserting code in your application that simply prints program values to a log file. These log values can range from critical internal values like the result of an intermediate calculation, to an electronic version of bread crumbs that simply says, “I made it to the error checking state”. But if you are going to include this sort of logic, you are also going to need some way to control it. After all, we are trying to avoid creating special “debug” versions of our code, but every installation doesn’t need to be generating debug files all the time — this where command line arguments comes into play.

The basic idea is to go through you code and identify places where information is available that would be of potential value in debugging, and then create VIs that will write that information to a file. To save memory, you can even make these trace VIs dynamic so they are only loaded if they are enabled. But how do you enable them? A structure I often use is to assign each potential trace operation one bit of a U32 number. I pass the bit-mapped number into the application using a command line parameter, and store it in a FGV. Then each trace operation looks at its bit in the number and only generates its trace if the bit is set.

Putting theory into practice

To see how this approach would actually work, let’s add some trace capability to our test bed application. The following figure shows the mappings of trace operations to bits:

bit mappings

Note that we will actually be using 10 bits because three of the trace operations are in our reentrant temperature controller state machine, and we want to be able to control the trace for each clone individually. Next, we want to define the syntax for the command line parameter. To keep it simple, we will use the structure I showed above, where the value following the “d” is the bit-mapped number that will provide our enables. To generate this number, simply total the numeric values for each on the trace options you wish enabled. For example, the argument “d1” would turn on just the Sample Period Change trace, while the argument “d146” (2+16+128) would turn on the Fan State Change trace for all three temperature controllers.

In case you’re wondering how users are supposed to generate these magic numbers, that’s easy — they don’t. The intended use is that a support person will tell the customer what to enter with the instruction, “When you see the problem we are troubleshooting, go to the directory where the application is installed. You will find there a file named ‘trace.log’. Email it to us.”

Reading the arguments

In the LabVIEW environment, you gain access to the command-line arguments by reading the Command Line Arguments application property. The data is returned as a 1D array of string that LabVIEW creates using the space as a delimiter between elements. This property is always available in the development environment, but in a runtime system you have to enable it in the Advanced section of the application builder parameters. To retrieve our debugging parameter I have built a VI that searches this array for the first element that starts with the letter “d” and then converts the remainder of the string into a number.

initialize debugging

Once it has isolated the number, the code converts it into a Boolean array and stores the result in a FGV for later use.

Creating the traces

With the array of Boolean values safely tucked away in a FGV, we can begin building the code that implements the trace functionality. However, not being stupid, the first thing I do is create a subVI that will format the trace entries in a standard way. Assuming there were no errors generated before the trace operation is called, each entry will consist of a timestamp, the name of the trace point generating the data, and the trace data itself.

save trace data

If an error did occur earlier in the error chain, the trace subVI instead saves the error. Ideally, all errors should be saved to the error log as part of your program’s normal operation — but mistakes in propagating errors can easily happen.

save trace data - error

With that foundation laid, we move up a layer in the code hierarchy, and the first application-specific trace option we create is one that will record changes in the sample period that the two acquisition processes use to pace their operation.

sample period trace option

Thanks to the subVI created a moment ago, you can see that all the trace VI really has to do is check to make sure that its bit is set in the array of Boolean enables from the FGV and (if it is) write pass the trace data to the subVI. To simplify things, I also built a subVI to encapsulate the indexing process and return an enumeration giving the trace state.

The remaining options we want to implement are for the temperature controller. First, we want to save a trace message whenever the fan or cooler state changes. This is the trace VI for the Fans State Change trace. The Cooler State Change trace is almost identical:

fan state change trace option

A new challenge that this code needs to address is that for it to work properly, the VI needs to know which of three clones is calling it. To enable this new requirement, I added a new parameter to the data that is passed to the clones when they are launched. Called Launch Order it tells each clone its ordinal position in the launch sequence. With that number and a little basic math, the trace VI can calculate the correct index for its enable.

The last trace operation (also in the temperature controller) records the state machine’s state transitions. Due to its similarity to the others, it’s not worth showing its code. However, this trace option is a good example of a type of operation that can be very helpful, but which you want to be sure isn’t left enabled for an extended period of time. Every state change will be recorded — an operation that can generate a lot of entries really fast. Consequently, if you put in this sort of trace you should think about augmenting it, for example, with the ability to only report certain state transitions, or perhaps only save the last 100 trace messages.

Testing our work

To see this code in action you can build an executable, or run it from within the development environment. In either case you need to start by creating a shortcut on your desktop that points to either LabVIEW.exe or Testbed.exe. Next, edit the shortcut to add in a debug argument with a numeric value of you choosing.

Now use the shortcut to launch LabVIEW or the testbed application and monitor the project directory. A trace.log file will appear and be updated as the application executes. A handy tip is if you are going to be testing a number of set configurations, it can save time to create a number of shortcuts with different debug configurations preconfigured.

Testbed Application Release 14
Toolbox Release 7

So this is one use I came up with for this technique. Where does you intuition and imagination say to try it? In the mean time, the requisite teaser for the next post…

Objectifying LabVIEW

The National Instruments training course on object-oriented programming recommends trying it in small doses as you are learning — an attitude I heartily endorse. Next time we are going to take a small step in that direction by developing a more general solution for at least part of our data management. Along the way we’ll discover that while it may not be the ultimate programming paradigm that will revolutionize civilization as we know it (as some Object-Orient Fanboys proclaim) it can still be real useful — even in small doses.

Until Next Time…

Mike…

How to Make Dynamic SubVIs

The last two posts discussed different ways to use dynamic linking and calling in situations where you want the target VI to run in parallel with the rest of the application. Another major use case for dynamic calling is to create software plugins.

I use the term “plugin architecture” to indicate a technique that strives to simplify code by facilitating runtime changes to limited portions of the code while allowing the basic logic for the function as a whole to remain intact and unchanged. For example, say you are implementing a control algorithm that has, for the sake of argument, two inputs: a position and a load. As long as the readings are accurate, timely and in the proper units, most of the control algorithm doesn’t care how these inputs are measured. It would, therefore, simplify the code base as a whole to incorporate a technique that allows the reuse all the common code by simply “plugging in” different acquisition modules that support the various different types of sensors that can measure position and load.

This isn’t rocket science

This goal might sound lofty, but the hurdle for getting into it is actually pretty low. In fact, when teach the LabVIEW Core 1 and 2 week, I will sometimes introduce what LabVIEW can do by demonstrating a simple plugin architecture that only uses concepts introduced and discussed in those two classes.

I start the demo by opening an application that I have written to implement a simple calculator. It has two numeric inputs labeled “X” and “Y” and an indicator labeled “Output”. The only other control is a popup menu that lists the two math operations it can perform: addition and subtraction. I first show that you can use the simple program to add and subtract numbers.

I then comment that it would be nice if my program could multiply numbers as well. So I drag the program window to one side but leave it running. I then open a new window and add two inputs and an output (labeled like the program). On the block diagram I drop down a multiplier, wire it up, and save the VI with the name Multiply.vi. After closing that VI’s front panel, I tell the class that although they just watched me create the ability to multiply numbers, the program which is already running has already acquired it. At this point, I pull the program front panel back over and show that the menu which only moments before said “Add” and “Subtract”, now has a third option: “Multiply”. Moreover, the new selection does indeed allow me to now multiply numbers. Finally, I make the point that this capability to dynamically expand and change functionality was created using nothing but things that they will be learning in the coming week.

While the students at this point might not be cheering, they are awake and have some motivation to learn. You better believe that when I show them the code Friday afternoon and they can recognize how the program works, they are excited. So let’s see what I can do now to motivate and excite you…

It’s all about the packaging

As we begin to get into the following use cases you should notice that the actual code needed to implement each solution is not really very complicated. If fact, in the following sections we will be dealing with many of the same functions as when we were dynamically launching separate processes. The tricky part here is that we need to fit all the data management and launch logic in a space that would otherwise be occupied by a single VI. For this reason we need to be very careful when packaging this code. To demonstrate what I mean, let’s consider three common use cases that all call a simple test VI (called amazingly enough Test.vi) that simply returns a random number. I have ordered these cases such that each example builds on what went before:

System Initialization

I like to start here when explaining these concepts because it is the simplest structure and so is easy to understand.

System Initialization

Here you see laid out all the basic pieces required to dynamically call a subVI. The first VI (the one marked “Dummy”) is the one we will use through-out this post to represent the data management needed to get the path to the VI that will be called dynamically. Depending upon your application this could come from an INI file, database, or what have you. We won’t go into a lot of detail with that VI because we have discussed the various techniques pretty thoroughly in other posts.

You should recognize the next node (Open VI Reference) as we have already used it a lot. It opens a reference to the VI specified in the input VI name or path. This action also has the effect of loading the VI into memory. Just as statically subVIs can be reentrant, you can specify it here, as well, if needed.

Once the VI is open, we pass its reference to a node that looks a lot like the Start Asynchronous Call we have already used. This node is Call by Reference and like the prior one it expects a strict VI reference and in return provides a connector pane for passing data to the target. However, unlike the asynchronous call, this node always waits until the target finishes executing before continuing, hence its representation of the target’s connector pane also allows us to get data from the target.

Finally, in order to remove the target VI from memory, we need to close the reference when we are done with it — which is what the last node does.

When using this technique it is important to keep in mind that, because it loads, executes and unloads the VI all at once, it is very inefficient when used is a situation where it will be run multiple times. Sort of like trying to use an Express VI inside a loop — but worse.

However, one place where this technique can be used very effectively is doing things like system initialization. Its not uncommon for large and complex applications to require an equally large and complex initialization process, especially the first time they are run. Although you could just write a VI to do this initialization and install it in the program, why should you burden your program with a bunch of code that may only execute one time — ever? This technique allows you to only load and execute a VI if you actually need it.

Function Substitution

This use case is perhaps the most common of the three. It uses the same nodes we just saw, but because it can be used essentially anywhere, the packaging has to be very efficient. A solution I often use is to create a mini state-machine that has states for loading, executing and unloading the target VI, as well as one more for deciding what to do first. The user interface has one (enumerated) control that allows you to specify whether you want to Load the target, Run the target, or Unload the target.

The inputs are pretty simple, and the state-machine logic mirrors that simplicity. If the function requested is Load, the Startup state transitions directly to the Open VI state which opens and buffers the VI reference. Here is the code for these two states:

Function substitution - Startup - Load

Function substitution - Open VI

If the function requested is Run (the input’s default value, by the way), the Startup state first checks to see if the VI reference is valid and if it is, transitions to the Execute state.

Function substitution - Startup - Run

Function substitution - Execute

If the reference is not valid, execution instead continues with the Open VI state which returns to the Execute state after opening the reference.

Finally, if the function requested is Unload, the Startup state transitions directly to the Close VI state:

Function substitution - Startup - Unload

Function substitution - Close VI

The result of all this work is a flexible VI that can be used in a variety of different ways depending upon system requirements. For example, if the target VI loads quickly or the calling process doesn’t have tight timing constraints, you can just install it and let the first call to both load the VI into memory and run it. Alternately, if you don’t want the first call to be delayed by that initial load, you can call it once in a non-time-critical part of the code to just Load the target and then Run it as many times as you like later.

In the same way, the Unload function, which normally isn’t needed, can be used to release the memory resources used by a large dynamic subVI when you know that you aren’t going to be needing it for a while.

This approach could even be extended to create a simple test executive that dynamically loads and unloads whole sets of VIs. In such a situation, though, you probably don’t want to be tied to a single connector pane, so you should consider changing the VI reference to non-strict and modifying the Execute state to use our old friend the Run VI method like so.

Function substitution - Execute - Run VI Method

Interprocess Communication

This use case is in many ways the most expansive of the three because it supports communications between different processes regardless of their physical location. In terms of code, there is very little difference between this case and the previous one. In fact, the only change that is really required is to the Load state. This is what the network-enabled version looks like:

Function substitution - Open VI - IPC

That new icon in front of Open VI Reference is the one that makes the magic. Its name is Open Application Reference and its job is to return a reference to the instance of LabVIEW that is hosting the VI that you want to access. To make this connection, you need to know the host-name or IP address of the computer running the LabVIEW application, and the port number the instance is using for incoming connections. If the application that you are wanting to access in on your own computer, you can either leave the host name string empty or use the name localhost. The port can be any number that isn’t already being used. One common people mistake is to simply accept the default value, which is the port that LabVIEW listens to by default. This causes problems when they go to test their application because now there are two processes (the LabVIEW development environment and their application) trying to use the same port.

An important point to remember is that while the Call by Reference node passes data back and forth, the called VI actually executes on the remote system. Hence, this sort of operation works independent of the target system’s operating system or even the version of LabVIEW that it is running.

The tasks for setting up a system to use this technique involves properly configuring the application to which you are going to be connecting — though it often doesn’t require any code changes. This amazing benefit is possible because the underlying networking is handled by the runtime engine, not the application code you write. I have on a couple occasions come into a place and added remote control functionality to an old application that was not even designed with that capability in mind.

You will, however, have to make changes to the target application’s INI file to enable networking, And you, obviously, will need to have access to the application’s source code so you know what VIs to call. Likewise, if you are accessing a computer outside your local network there can be a variety of network communications details to sort out that are beyond the scope of this post. One other thing to bear in mind is that it is always easier to connect to a remote VI if it is already in memory. The reason for this fact is that if the VI is already in memory all you need to know is the VI’s name. If you are also loading it into memory, you have to know the complete path — the annotation of which can change between platforms.

But assuming you establish the connection, what exactly can you do with it? The answer to that lies in what VIs you choose to execute from the remote process. If you execute a VI that fires an event, that event gets fired on the remote system, so you can use it as a channel for controlling that application.

Alternately, say you chose to execute a VI that is a function global variable (FGV). Depending on whether you are reading from or writing to the FGV, you are now either passing data to the remote system, or collecting data from it. By the way, this is the method I still typically use for passing data over a network between LabVIEW applications — not network-enabled shared variables. Unlike this later “enhancement”, the dynamic calling of a FGV doesn’t need to be “deployed”, is more memory efficient, has a smaller code footprint, is far easier to troubleshoot if there is a problem, and works across all releases of LabVIEW back to Version 6.

The only real limit to what you can do with this technique is your imagination.

So there are the three major use cases for the dynamic linking of subVIs. This discussion is by no means a complete consideration of the topic, but hopefully it will whet your appetite to experiment a bit. The link below is to an archive containing all three examples to get you started.

Dynamic Linking Examples

Next time: Command Line Parameters are not a relic of the past.

If you have spent any time poking into some of the more esoteric corners of the application builder, you may have noticed a checkbox in the Advanced section labeled, “Pass all command line arguments to application”, and wondered what that is all about. Well, wonder no more, that little checkbox is what we are going to discuss next time out — and while we’re at it I’ll cover a really neat use for it.

Until next time…

Mike…

Raising the Bar on Dynamic Linking Even Further

Important: Before we get started this week, if any of you have downloaded the code from last week and have had problems with it, please update you working copy with what is currently in SVN. In working on this post, I found some “issues” — including the significant problem that the database that defines everything didn’t get included in the repository release. The current contents of the repository should fix the problems, and I am sorry for any hair-loss and/or gnashing of teeth these problems might have caused.

Also, be sure that if you plan to run this code in the development environment that you name the directory where it is located “testbed”.

These days, the most common way of implementing the dynamic calling of VIs in LabVIEW is through the Start Asynchronous Call node. In addition to being efficient, this technique is also very convenient in terms of passing data to the VI that is being called. Because it replicates the VI’s connector pane on the node, all you have to do is wire to terminals. However, this convenience comes at a price. For this type of call to work you have to know ahead of time what the connector pane of the VI being called looks like. This constraint can be a problem because it is not uncommon for situations to arise where you are wanting to dynamically call code the connector pane of which varies, is irrelevant (because no data is being passed), or is unknown. As you should by now be coming to expect, LabVIEW has you covered for those situations as well.

Where There’s a Will, There’s a Way Method

Over the years as LabVIEW developed as a language, its inherent object orientation began to become more obvious. For example, when VI Server was introduced it provided a very structured way of interacting with various objects within LabVIEW, as well as LabVIEW itself. With VI Server you can control where things appear on the front panel, how they look and even how LabVIEW itself operates. Although it didn’t reach its full expression until the Scripting API was released, the potential even existed to create LabVIEW code that wrote LabVIEW code.

We won’t be needing anything that complex, however, to accomplish our goal of dynamically launching a VI where we don’t have advance knowledge of its connector pane. In fact the part of VI Server that we will be looking at here is one of the oldest and most stable — the VI object interface. Just as you can get references to front panel controls, indicators and decorations, you can also get references to VIs as a whole.

Like control references, VI references come in two basic forms: strict and non-strict. To recap, a strict control reference contains added information that allows it to represent a particular instance of the given type of control. For example, a strict cluster control reference knows the structure, or datatype, of a particular cluster. By contrast, a non-strict cluster reference knows the control is a cluster, but can’t directly tell you what the various items are that make up the cluster.

In the same way, strict VI references, like we have been using to dynamically launch VIs, know a great deal about a specific class of VI, including the structure of its connector pane. This is why the Start Asynchronous Call node can show the connector pane of the target VI. However, as stated earlier, this nice feature only works with VIs that have connector panes exactly matching the prototype. As you might suspect, the solution to this problem is to use a non-strict VI reference, but that means we need to change our approach to dynamic launching a bit. Instead of using a special node, we’ll use standard VI Server methods to interact with and run VIs.

Mix and Match

To see how this discussion applies to our testbed code base, consider that to this point we have used a single technique to launch all the processes associated with the application. Of course to make that approach work required one teeny tiny hack. Remember when we added to the data source processes an input that tells them “who they are”? Well, that modification necessitated a change to the VIs’ connector panes, and because we were launching all the processes the same way, I had to make the same change to all the processes — even those that didn’t need the added input, like the GUI and the exception handler.

So big deal, right? It was only one control, and it only affected 2 VIs. Well maybe in this case it isn’t a huge issue, but what if it weren’t 2 VIs that needed to be changed, but 5 or 6? Or what if all the various processes needed different things to allow them to initialize themselves? Now we have a problem.

The first step to address this situation was actually taken some time ago when the launcher was designed to support more than one launch methodology. You’ll remember last week when creating the dynamically launched clones, we didn’t have to modify the launcher because it was written from day one to support reentrant VIs. What we have to do now is expand on this existing ability to mix and match VIs with launch methodologies to include two new options in the Process Type.ctl typedef. Here’s what the code for the first addition looks like:

run method - nonreentrant

As before, we start by opening a reference to the target VI, but this time it’s a non-strict reference. Next, we invoke the Run VI method, which has two inputs. The first input specifies whether we want to wait the target VI to finish executing before continuing, and we set it to false. The second parameter is named somewhat obscurely, Auto Dispose Ref. What it does is specify what to do with the VI reference after the VI is launched. In its default state (false) the calling VI retains the VI reference and is responsible for closing it to remove the target VI from memory. In addition, if the calling VI retains the reference to the dynamic VI, when the caller quits, the dynamic VI is also aborted and removed from memory. On the other hand, when this input is set to true, the reference is handed off to the target VI so the reference doesn’t close until the dynamic VI quits — which is what we want.

The other new launch option is like the first, except it connects the option constant that tells the Open VI Reference to open a reference to a reentrant clone. Other than that, it works exactly the same.

run method - reentrant

So with these two new launch methodologies created, all we have to do is change the database configuration for the GUI and Exception Handler processes to use the nonreentrant version of the Run VI method and we are done right? Well not quite…

One of the “quirks” of the Run VI method is that although it does start a VI executing, if that VI is configured to open its front panel when run (like our GUI is), the open operation never gets triggered and the front panel will stay closed. The result is that the VI will be open and running, you just won’t be able to see it.

To compensate for this effect (and the corresponding effect that the front panel won’t automatically close when the VI finishes), we need to add to the GUI a couple VIs from the toolbox that manage the opening and closing of the GUI’s the front panel.

open front panel

That’s the opener there, the last one in line after all the initialization code. This placement is important because it means that nearly all the interface initialization will be completed before the front panel opens. The result is much more professional looking. By the way, this improved appearance is why I rarely use the option to automatically open a VI’s front panel when it is run.

close the front panel

And here is the closer. The input parameter forces the front panel closed in the runtime, but allows it to stay open during development — a helpful feature if there was an error.

Where do we go from here?

So that is the basics of this technique, but there is one more point that needs to be covered. Earlier I talked about flexibility in passing data, so how do you pass data with this API? Well, we ran the VI using a method, so as you would expect, there is other methods that allow you to read or set the value of front panel controls. This is what the interface to the Control Value Set method looks like.

the set control value method

It has two input parameters: a string that is the label of the control you want to manipulate, and a variant that accepts the control’s new data value. Note that because LabVIEW has no way of knowing a priori what the datatype should be, you can get a runtime error here if you pass an incorrect datatype. Obviously, using this method your code can only set one control value at a time so unless you only have 1 or 2 controls that you know you will need to set, this method will often end up inside a loop like so:

set control value in a loop

…but this brings up an interesting, and perhaps exciting, idea. Where can we get that array of control name and value pairs? Would it not be a simple process to create tables in our database to hold this information? And having done that would you have not created a system that is supremely (yet simply) reconfigurable. This technique also works well with processes that don’t need any input parameters to be set. The loop for configuring control values passes the VI reference and error cluster through on shift registers and auto-indexes on the array of control name/value pairs. Consequently, if a given VI has no input parameters, the array will be empty and the loop will execute 0 times — effectively bypassing the loop with no added logic needed. By the way, this is an important principle to bear in mind at all times: Whenever possible avoid “special cases” that have to be managed by case structures or other artificial constructs.

More to Come

Over two consecutive posts, when have now covered two major use cases for using dynamic linking: VIs that will run as separate processes. But there is another large use case that we will look at the next time we get together: How do you dynamically link code that isn’t a separate process, but logically is a subVI?

Testbed Application &mdash Release 12

Toolbox — Release 6

Until next time…

Mike…

Taking Flexibility to the Next Level — Dynamic Linking

When people start using LabVIEW they always find fascinating the way it allows them to assemble complex functionality from simple building blocks, called subVIs. However there are a variety of use cases where — for very legitimate reasons — the usual technique for interconnecting subVIs with wires just won’t do. One thing that many of these use cases have in common is the desire to put off until runtime the decision of what code we want the program to execute next. Noting the power of being able to add functionality to our computers by plugging in a new card, or USB dongle, a common wish is to find a way to do do the same thing in software, a way to create software plugins.

In fact the potential benefits of this sort of structure were so great that in the early days of LabVIEW’s existence, developers would go to absurd lengths to realize even a small bit of it. For example, the only way originally of creating software plugins was to take advantage of the fact that LabVIEW links to subVIs by name and manually change the names of files to select the one you wanted to use before opening the top-level VI. If you’re wondering how that impacted building an application, that wasn’t a problem — there was no application builder!

Thankfully, today we have many different options for creating a true plugin architecture through dynamically calling and linking VIs. To explore those options I’m going consider over the next few posts a few of the basic use cases, and demonstrate the techniques that serves best for each one.

What We Have Already Seen

A good place to start this exploration is a use case with which we are already familiar — the one embodied in our testbed application. When I originally presented the idea of structuring the program as a group of independent processes that were dynamically launched, I spoke about it in terms of promoting a kind of modularization that simplified the code, made the application as a whole more robust, and fostered reusability. While all that is true, there is another side to that coin. By providing you with the ability to select at runtime the capabilities of the software, it opens the door for dramatically improved scalability. To see how that would work, consider the following scenario.

The most recent change to the testbed was to create a state-machine temperature controller that was (as I said in the post) only suitable for controlling the temperature of a “…hen-house, dog house or out house…”. But let’s say you have more than 1 hen-house, dog house or out house that needs temperature control. How should we handle that? One solution that you often see used is to simply duplicate that one VI, but then what if the number of “houses” changes? You could be left in the position of constantly modifying the application to add or subtract resources. Let me show you a better way, a way to reuse the exact same VI as many (or as few) times as might be needed, but without needing to modify the code to change the number if instances created. The trick is to make the code reentrant.

The basic idea behind a reentrant VI is that each time it is called in a program, LabVIEW reuses the same code, but gives each instance its own memory space. The result is the same as if you had multiple copies of the same VI. This basic operating principle is the same regardless of whether the reentrant VI is linked into your program statically, or is being called dynamically. When making a call to a reentrant VI, the resulting instance running in memory is called a clone. It’s important to remember that while all the clones of a given VI will use the same common code, they each have (in addition to their own memory space) their own block diagram for debugging and front panel for user interaction. In some ways it’s as though you are dynamically creating new VIs out of thin air!

Bring in the clones…

So what do you need to do to embed this magic in your program, you ask? Well, perhaps, not as much as you would think…

Reentrancy

The first thing, obviously, is that in order to be cloneable, the VI must be reentrant. But for many people, this feature can be at first confusing and more than a bit intimidating (I know it was for me). The challenge from the perspective of LabVIEW is that, unlike most languages, LabVIEW actually supports two kinds of reentrancy. The easy one is the one that creates “Preallocated” clones, it works the way you see it described if you look up the term “reentrant” online. For many, the confusing part is the other kind of reentrancy, the “Shared” clones. I know the first time I saw the term, it stuck me as an oxymoron of the first order. As I understood reentrancy, the whole point of reentrant execution was that clones used preallocated memory spaces that didn’t get shared. So shared clones, what is that about? In order to answer that, we need to consider how LabVIEW execution works.

Normally, VI execution is blocking. By that I mean that two instances of the same VI cannot run at the same time because they share the same memory space. However, because they have their own memory spaces, multiple instances of reentrant VIs can execute in parallel. Consequently, when writing very low-level VIs that are going to be used in many, many places throughout your code, one would like to make them reentrant so the instances are not all blocking each other. However if you did that willy-nilly, you could develop a problem: memory. You could end up preallocating a lot of memory for dozens or even hundreds of instances that don’t actually run very often, or perhaps ever. To address that problem, LabVIEW created the concept of shared reentrancy that splits the difference between nonreentrant execution on the one hand, and normal reentrant execution (which LabVIEW now calls preallocated), on the other.

Shared cloning creates a pool of a predefined number of sharable memory spaces that the reentrant code can use. Now whenever a particular reentrant function is called, LabVIEW can go to its pool of memory spaces and use a memory spaces that isn’t busy right then. If all the copies in the pool are busy, LabVIEW creates a new memory space for the VI, adds it to the pool and then uses it. All in all, it’s a pretty nice feature that we will see the need to use later…

Parameterized Identity

The next thing clones need is a way tell themselves apart. As a case in point, you can have 3 houses and 3 clones of our control process, but if all 3 clones try to control the same house, you still haven’t accomplished anything useful. Clone 1 has to be able to tell that it is responsible for house 1. Likewise, clone 2 has to know that it is handling house 2, and clone 3 must direct its efforts to controlling house 3. The way to accomplish this assignment is through a process called parameterization, or passing to the clone an input parameter that it can then use to obtain the configuration information it needs to operate.

This area is one of those places where having an adequate data management infrastructure in place (read: database) really pays big dividends. Ideally, you should be able to provide a clone with an identity through a single value, like a name. Other times, the various clones may be controlling specific test systems, so perhaps all you would need is the ID number of the system assigned to the clone. Regardless of the exact nature of that identifying value, with a database in place the clone can use that name or number to query the database for the specific setup information that it needs.

Functional Completeness

A good clone should be functionally complete. By that, I mean that a clone needs to be a complete package unto itself, not needing a lot of detailed external interactions. A few high-level controls are fine, but you don’t want to expose too much detail.

As an example of what I mean, consider the following situation from life. Say, you have a new employee in your department that needs to fill out some sort of form from HR. While it might be reasonable to explain what specific information is needed in one field or another, you shouldn’t have to take the time to explain that the side of the form to fill out is the one with the printing on it. You also shouldn’t have to describe the color of ink to use or the hand in which the person should hold the pen — or for that matter, that they should hold the pen in their hand and not between their toes! The only instruction that should be needed is, “Go fill out this form.”

Likewise with a clone, you should be able to say things like, “This is the code that does the out house temperature control”, or, “This is the process that interfaces with the database.” A good way of judging how well you are doing in meeting this “completeness” goal is to consider whether you can change the details of what the clone does without impacting the rest of the application.

A principle that can be supportive of this sort of completeness is to make sure the clone’s code isn’t burdened with extraneous functionality like extensive GUIs. Remember that adding in nonessential functionality limits the applicability of that code to only those situations where that added functionality is needed. As a practical matter, you particularly don’t want clones to have the ability to open dialog boxes. In case you aren’t clear on the reasoning behind that injunction, consider the pandemonium that could result if you have 15 or 20 clones running and something happens that causes all of them to start opening dialog boxes simultaneously.

Managing Common Resources

Finally, because you can potentially have a large number of clones running at the same time, you need to carefully consider parallel access to common resources. Interfaces that might work well for 2 or 3 clones could run into bandwidth problems when there are a couple dozen. Moreover, as the number of simultaneous accesses increase, so does the potential for access conflicts and race conditions. For example, I can’t tell you how many times I have seen systems crash and burn over something as simple as two processes trying to write to the same file at the same time.

If you have a common resource that could become a bottleneck or produce a race condition, a solution that often works well is to create a separate process with the sole responsibility of managing that resource.

Cloning our Temperature Controller

So what do we need to do in order to close our temperature controller? We know that in order to be cloned, the VI needs to be reentrant, so Step One is to turn that feature on by going to the Execution page of the VI Properties and selecting the option for Preallocated Clones.

reentrant setting

That one setting handles the top-level, but what about the subVIs? They could still block each other and prevent the cloned controllers from running unobstructed. To prevent that possibility, you should go through the subVIs looking for routines that are called regularly, and make them shared clones. In the current code, there is one exception to this advice — and we’ll cover it next.

With the applicable code set to being reentrant, we need to consider the identity issue. That matter is, to an extent, already being handled in the existing code because even nonreentrant VIs sometimes need to be told who they are. All we need to do is expand our usage of the My Name label we are already passing to the dynamically launched VIs by using it to look-up the configuration data for each clone.

The nonreentrant version of our temperature controller state-machine already had a VI for looking up the state-machine setups in the database, all we have to do is modify it so it accepts the My Name value as an input. Here is the modified version of the look-up VI.

load state machine settings

This is the VI that I said earlier that we do not want to make a shared clone, and there are two reasons. First, the VI only gets called once so there would be no benefit. Second, when you consider what the VI is designed to do, making it a shared clone would actually be counterproductive. See the shift registers? They are there to buffer the results of the queries to reduce the number of database accesses that the application has to make. In other words they are there specifically to created a shared memory space, so making it a clone would defeat that purpose.

Next we need to address the dialog box in the data saving routine. It really isn’t a good idea to give users the responsibility to correctly save data files anyway, so let’s change it.

modified save data

This modification saves the file with a dynamically generated name, to a predefined location, in this case the “Documents” directory associated with the current Windows user. Note that part of the file name is a timestamp consisting of the year, month, day, hour, minutes and second the file was created. I like this filename format because (assuming a 4-digit number for year and 2 digits for everything else, and a 24 hour clock for hours), files named in this way will always sort by name in correct chronological order.

Beyond that we are pretty much done. The existing code was already concise and had no user interface to speak of — just a few controls that we would want to have for troubleshooting purposes anyway. The only other thing needed is to define the clones that we want in the database. When you run the modified code, you will see three clones with differing update rates — but you can create as many as you want by simply adding them (and their setup parameters) to the database.

We are almost done for now, so hopefully you see that in some ways dynamic linking isn’t what this post is really about. Oh, I have presented a little bit of technique, and a few code modification, but the real lesson is that expanding the reach of you code in this way is not hard, as long as the code you are working with is well-designed from the beginning. Often you will hear people talking about all the modifications that they had to make in order to make their code cloneable, but if you really look at it you see that most of the modifications were in actuality fixing mistakes that they should never have made in the first place.

What Now

This should be enough for everybody to digest for now, so next week we’ll look at the other way to launch dynamic VIs — and why you would want to use it. (Hint: It addresses a problem that right now you don’t even know you have.)

Testbed Application — Release 11

Toolbox — Release 5

Until next time…

Mike…

Building a Proper LabVIEW State Machine Design Pattern – Pt 2

Last week’s post was rather long because (as is often the case in this work) there was a lot we had to go over before we could start writing code. That article ended with the presentation of a state diagram describing a state machine for a very simple temperature controller. This week we have on our plate the task of turning that state diagram into LabVIEW code that implements the state machine functionality.

The first step in that process is to consider in more detail the temperature control process that the state diagram I presented last week described in broad terms. By the way, as we go through this discussion please remember that the point of this exercise is to learn about state machines — not temperature control techniques. So unless your application is to manage the internal temperature of a hen-house, dog-house or out-house; don’t use this temperature control algorithm anywhere.

How the demo works

The demonstration is simulating temperature control for an exothermic process — which is to say, a process that tends to warm or release heat into the environment over time. To control the temperature, the system has two resources at its disposal: an exhaust fans and a cooler. Because the cooler is actively putting cool air into the area, it has a very dramatic effect on temperature. The fan, on the other hand, has a much smaller effect because it is just reduces the heat through increased ventilation.

When the system starts, the state machine is simply monitoring the area temperature and as long as the temp is below a defined “High Warning Level” it does nothing. When that level is exceeded, the system turns on the fan, as shown by the fan light coming on. In this state, the temperature will continue to rise, but at a slower rate.

Eventually the temperature will, exceed the “High Error Level” and when it does, the system will turn on the cooler (it has a light too). The cooler will cause the temperature to start dropping. When the temperature drops below the “Low Warning Level” the fan will turn off. This action will reduce the cooling rate, but not stop it completely. When the temperature reaches the “Low Error Level”, the cooler will turn off and the temperature will start rising again.

So let’s look at how our state machine will implement that functionality.

“State”-ly Descriptions

As I stated last week, the basic structure is an event structure with most of the state machine functionality built into the timeout case. To get us started with a little perspective, this is what the structure as a whole looks like.

State Machine Overview

Obviously, from the earlier description, this state machine will need some internal data in order to perform its work. Some of the data (the 4 limit values and the sample interval) is stored in the database, while others are generated dynamically as the state machine executes. Regardless of its source, all of this data is stored in a cluster, and you can see two of the values that it contains being unbundled for use. Timeout is the timeout value for the event structure and is initialized to zero. The Mode value is an enumeration with two values. In the Startup case is the logic that implements startup error checking and reads the initial setup values from the database. When it finishes, it sets Mode to its other value: Run. This is where you will find the bulk of the state machine logic. Note that while I don’t implement it here, this logic could be expanded to provide the ability to do such things as pause the state machine.

The following sections describe the function of each state and shows the code that implements it.

Initialization

This state is responsible for getting everything initialized and ready to run.

Initialization State

In a real system, the logic represented here by a single hardware initialization VI would be responsible for initializing the data acquisition, verifying that the system is capable of communicating with the fan and cooler, and reading their operational states. Consequently, this logic might be 1 subVI or there might be 2 or 3 subVIs. The important point is to not show too much detail in various states. Use subVIs. Likewise, do not try to expand on the structure by adding multiple initialization states.

Finally, note that while the selection logic for the next state may appear to be a default transition, it isn’t. The little subVI outside the case structure actually creates a two-way branch in the logic. If the incoming error cluster is good, the incoming state transitions (in this case, Read Input) is passed through unmodified. However, if an error is present, the state machine will instead branch to the Error state where the problem can be addressed as needed.

Read Input

This state is responsible for reading the current temperature (referred to as the Process Variable) and updating the state machine data cluster.

Read Input State

In addition to updating the last value, there are a couple other values that it sets. Both of these values relate to how the acquisition delay is implemented in the state machine. The first of these is the Timeout value, and since we want no delay between states, we set this to zero. The other value is the Last Sample Time. It is a timestamp indicating when the reading was made. You’ll see in a bit how these values are used.

This state also updates two front panel indicators, the graph and a troubleshooting value.

Test Fan Limits

This state incorporates a subVI that analyzes the data contained in the state machine data to determine whether or not the fan needs to change state.

Test Fan Limits State

The selector in this state results in what is the potential for a 3-way branch. If a threshold has been crossed, the next state will be Set Fan, if it has not, the next state will be Test Cooler Limits, and if an error has occurred, the next state will be Error.

Set Fan

Since the fan can only be on or off, all this state needs to do is reverse its current operating condition.

Set Fan State

In addition to the subVI toggling the fan on or off, the resulting Fan State is unbundled from the state machine data and the value is used to control the fan LED.

Test Cooler Limits

This state determines whether the cooler needs to be changed. It can also only be on or off.

Test Cooler Limits State

The logic here is very similar to that used to test the fan limits.

Set Cooler

Again, not unlike the corresponding fan control state, this state changes the cooler state and sets the cooler LED as needed.

Set Cooler State

Acquisition TO Wait

This state handles the part of the state machine that is in often the Achilles Heel of an implementation. How do we delay starting the control sequence again without incurring the inefficiency of polling?

Acquisition TO Wait State

The answer is to take advantage of the timeout that event structures already have. The heart of that capability is the timeout calculation VI, shown here:

Calculate Time Delay

Using inputs from the state machine data, the VI adds the Sample Interval to the Last Sample Time. The result is the time when the next sample will need to be taken. From that value, the code subtracts the current time and converts the difference into milliseconds. This value is stored back into the state machine data as the new timeout. This technique is very efficient because it requires no polling.

But wait, you say. This won’t work if some other event happens before the timeout expires! True. But that is very easy to handle. As an example, here is the modified event handler for the save data button.

Handling Interrupting Events

It looks just as it did before, but with one tiny addition. After reading, formatting and saving the data, the event handler calls the timeout calculation VI again to calculate a new timeout to the intended next sample time.

Error

This state handles errors that occur in the state machine. That being the case, it is the new home for our error VI.

Error State

Deinitialize

Finally, this state provides the way to stop the state machine and the VI running it. To reach this state, the event handler for the UDE that shuts down the application, will branch to this state. Because it is the last thing to execute before the VI terminates, you need to be sure that it includes everything you need to bring the system to a safe condition.

Deinitialize State

With those descriptions done, let’s look at how the code works.

The Code Running

When you look at the new Temperature Controller screen you’ll notice that in addition to the graph and the indicators showing the states of the fan and cooler, there are a couple of numbers below the LEDs. The top one is the amount of elapsed between the last two samples, the bottom one is the delay calculated for the acquisition timeout.

If you watch the program carefully as its running, you’ll notice something a bit odd. The elapsed time indicator shows a constant 10 seconds between updates (plus or minus a couple of milliseconds — which is about all you can hope for on a PC). However, the indicator showing the actual delay being applied is never anywhere near 10,000 milliseconds. Moreover, if you switch to one of the other screens and the back, the indicated delay can be considerably less than 10,000 milliseconds, but the elapsed time never budges from 10 seconds. So what gives?

What you are seeing in action is the delay recalculation, we talked about earlier. In order to better simulate a real-world system, I put a delay in the read function that pauses between 200- and 250-msec. Consequently when execution reaches the timeout calculation, we are already about 1/4 of a second into the 10 second delay. The calculation, however, automatically compensates for this delay because the timeout is always referenced to the time of the last measurement. The same thing happens if another event comes between successive data acquisitions.

On Deck

As always, if you have any questions, concerns, gripes or even (gasp!) complements, post ’em. If not feel free to use any of this logic as you see fit — and above all, play with the code see how you might modify it to do similar sorts of things.

Stay tuned. Next week we will take a deeper look at something we have used before, but not really discussed in detail: Dynamically Calling of VIs. I know there are people out there wondering about this, so it should be fun.

Until next time…

Mike…