Building a Proper LabVIEW State Machine Design Pattern – Pt 1

The other day I was looking at the statistics for this site and I noticed that one of the most popular post with readers was the one I wrote on the producer/consumer design pattern. Given that level of interest, it seemed appropriate to write a bit about another very common and very popular design pattern: the state machine. There’s a good reason for the state-machine’s popularity. It is an excellent, often ideal, way to deal with logic that is repetitive, or branches through many different paths. Although, it certainly isn’t the right design pattern for every application, it is a really good technique for translating a stateful process into LabVIEW code.

However, some of the functionality that state machines offer also means they can present development challenges. For example, they are far more demanding in terms of the design process, and consequently far less forgiving of errors in that process. As we have seen before with other topics, even the most basic discussion of how to properly design and use state machines is too big for a single post. Therefore, I will present one post to discuss the concepts and principles involved, and in a second post present a typical implementation.

State Machine Worst Practices

For some reason it seems like there has been a lot of discussions lately about state machine “best practices”. Unfortunately, some of the recommendations are simply not sound from the engineering standpoint. Meanwhile others fail to take advantage of all that LabVIEW offers because they attempt to mimic the implementation of state machines in primitive (i.e. text-based) languages. Therefore, rather than spinning out yet another “best practices” article, I think it might interesting to spend a bit of time discussing things to never do.

In describing bad habits to avoid, I think it’s often a good idea to start at the most general level and work our way down to the details. So let’s start with the most important mistake you can make.

1. Use the state machine as the underlying structure of your entire application

State machines are best suited for situations where you need to create a complex, cohesive, and tightly-coupled process. While an application might have processes within it that need to be tightly-coupled, you want the application as a whole to exhibit very low levels of coupling. In fact, much of the newest computer science research deprecates the usage of state machines by asserting that they are inherently brittle and non-maintainable.

While I won’t go that far, I do recognize that state machines are typically best suited for lower-level processes that rarely, if ever, appear to the user. For example, communications protocols are often described in terms of state machines and are excellent places to apply them. One big exception to this “no user-interface” rule is the “wizard” dialog box. Wizards will often be built around state machines precisely because they have complex interface functionality requirements.

2. Don’t start with a State Diagram

Ok, so you have a process that you feel will benefit from a state machine implementation. You can’t just jump in and start slinging code right and left. Before you start developing a state machine you need to create a State Diagram (also sometimes called a State Transition Diagram), to serve as a road-map of sorts for you during the development process. If you don’t take the time for this vital step, you are pretty much in the position of a builder that starts work on a large building with no blueprint. To be fair, design patterns exist that are less dependent upon having a completed, through design. However, those patterns tend to be very linear in structure, and so are easy to visualize in good dataflow code. By contrast, state machines are very non-linear in their structure so can be very difficult to develop and maintain. To keep straight what you are trying to accomplish, state machines need to be laid out carefully and very clearly. The unfortunate truth, however, is that state machines are often used for the exact opposite reason. There is a common myth that state machines require a minimum of design because if you get into trouble, you can always just, “add another state”. In fact, I believe that much of the bad advice you will get on state machines finds its basis in this myth.

But even if we buy the idea that state machines require a more through design, why insisted on State Diagrams? One of the things that design patterns do is foster their own particular way of visualizing solutions to programming problems. For example, I have been very candid about how a producer/consumer design pattern lends itself to thinking about applications as a collection of interacting processes. In the same way, state machines foster a viewpoint where the process being developed is seen as a virtual machine constructed from discrete states and the transitions between those states. Given that approach to problem solving, the state diagram is an ideal design tool because it allows you to visually represent the structure that the states and transitions create.

So what does it take to do a good state-machine design? First you need to understand the process — a huge topic on its own. There are many good books available on the topic, as well as several dedicated web sites. Second, having a suitable drawing program to create State Diagrams can be helpful, and one that I have used for some time is a free program called yEd. However fancy graphics aren’t absolutely necessary. You can create perfectly acceptable State Diagrams with nothing more than a paper, a pencil and a reasonably functional brain. I have even drawn them on a white board during a meeting with a client and saved them by taking a picture of them with my cell phone.

Moreover, drawing programs aren’t much help if you don’t know what to draw. The most important knowledge you can have is a firm understanding of what a state machine is. This is how Wikipedia defines a state machine:

A finite-state machine (FSM) or finite-state automaton (plural: automata), or simply a state machine, is a mathematical model of computation used to design both computer programs and sequential logic circuits. It is conceived as an abstract machine that can be in one of a finite number of states. The machine is in only one state at a time; the state it is in at any given time is called the current state. It can change from one state to another when initiated by a triggering event or condition; this is called a transition.

An important point to highlight in this description is that a state machine is at its core is a mathematical model — which itself implies a certain level of needed rigor in their design and implementation. The other point is that it is a model that consists of a “finite number of steps” that the machine moves between on the basis of well-defined events or conditions.

3. Ignore what a “state” is

Other common problems can arise when there is confusion over what constitutes a state. So let’s go back to Wikipedia for one more definition:

A state is a description of the status of a system that is waiting to execute a transition.

A state is, in short, a place where the code does something and then pauses while it waits for something else to happen. Now this “something” can be anything from the expiration of a timer to a response from a piece of equipment that a command had been completed successfully (or not). Too often people get this definition confused with that for a subVI. In fact one very common error is for a developer to treat states as though they were subroutines that they can simply “call” as needed.

4. Use strings for the state variable

The basic structure behind any state machine is that you have a loop that executes one state each time it iterates. To define execution order there is a State Variable that identifies the next state the machine will execute in response to an event or condition change. Although I once saw an interesting object-oriented implementation that used class objects to both identify the next state (via dynamic dispatch) and pass the state machine operational data (in the class data), in most cases there is a much simpler choice of datatype for the State Variable: string or enumeration.

Supposedly there is an ongoing discussion over which of these two datatypes make better state variables. The truth is that while I have heard many reasons for using strings as state variables, I have yet to hear a good one. I see two huge problems with string state variables. First, in the name of “flexibility” they foster the no-design ethic we discussed earlier. Think about it this way, if you know so little about the process you are implementing that you can’t even list the states involved, what in the world are you doing starting development? The second problem with state strings is that using them requires the developer to remember the names of all the states, and how to spell them, and how to capitalize them — or in a code maintenance situation, remember how somebody else spelled and capitalized them. Besides trying to remember that the guy two cubicles down can never seem to remember that “flexible” is spelled with an “i” and not an “a”, don’t forget that there is a large chunk of the planet that thinks words like “behavior” has a “u” in them…

By the way, not only should the state variable be an enumeration, it should be a typedef enumeration.

5. Turn checking the UI for events into a state

In the beginning, there were no events in LabVIEW and so state machines had to be built using what was available — a while loop, a shift register to carry the state variable, and a case structure to implement the various states. When events made their debut in Version 6 of LabVIEW, people began to consider how to integrate the two disparate approaches. Unfortunately, the idea that came to the front was to create a new state (typically called something like, Check UI) that would hold the event structure that handles all the events.

The correct approach is to basically turn that approach inside out and build the state machine inside the event structure — inside the timeout event to be precise. This technique as a number of advantages. To begin with, it allows the state machine to leverage the event structure as a mechanism for controlling the state machine. Secondly, it provides a very efficient mechanism for building state machines that require user interaction to operate.

Say you have a state machine that is basically a wizard that assists the user in setting up some part of your application. To create this interactivity, states in the timeout event would put a prompt on the front panel and sets the timeout for the next iteration to -1. When the user makes the required selection or enters the needed data, they click a “Next” button. The value change event handler for the button knows what state the state machine was last in, and so can send the state machine on to its next state by setting the timeout back to 0. Very neat and, thanks to the event-driven programming, very efficient.

On the other hand, if you are looking for a way to allow your program to lock-up and irritate your users, putting an event structure inside a state is a dandy technique. The problem is that all you need to stop your application in its tracks is one series of state transitions where the “Check UI” state doesn’t get called often enough, or at all. If someone clicks a button or changes something on the UI while those states are executing, LabVIEW will dutifully lock the front panel until the associated event is serviced — which of course can’t happen because the code can’t get to the event structure that would service it. Depending on how bad the overall code design is and the exact circumstances that caused the problem, this sort of lock-up can last several seconds, or be permanent requiring a restart.

6. Allow default state transitions

A default state transition is when State A always immediately transitions to State B. This sort of design practice is the logical equivalent of a sequence structure, and suffers from all the same problems. If you have two or more “states” with default transitions between them, you in reality have a single state that has been arbitrarily split into multiple pieces — pieces that hide the true structure of what the code is doing, complicates code maintenance and increases the potential for error. Plus, what happens if an error occurs, there’s a shutdown request, or anything else to which the code needs to respond? As with an actual sequence structure, you’re stuck going through the entire process.

7. Use a queue to communicate state transitions

Question: If default transitions are bad, why would anyone want to queue up several of them in a row?
Answer: They are too lazy to figure out exactly what they want to do so they create a bunch of pieces that they can assemble at runtime — and then call this kind of mess, “flexibility”. And even if you do come up with some sort of edge case where you might want to enqueue states, there are better ways of accomplishing it than having a queue drive the entire state machine.

Implementation Preview

So this is about all we have room for in this post. Next Monday I’ll demonstrate what I have been writing about by replacing the random number acquisition process in our testbed application with an updated bit of LabVIEW memorabilia. Many years ago the very first LabVIEW demo I saw was a simple “process control” demo. Basically it had a chart with a line on it that trended upwards until it reached a limit. At that point, an onscreen (black and white!) LED would come on indicating a virtual fan had been turned on and the line would start trending back down. When it hit a lower limit, the LED and the virtual fan would go off and the line would start trending back up again. With that early demonstration in mind, I came up with this State Diagram:

Demo State Machine

When we next get together, we’ll look at how I turn this diagram into a state-machine version of the original demo — but with color LEDs!

Until next time…

Mike…

Managing Shared Resources in a Subpanel-Based Interface

We have seen that building a system out of autonomous interacting processes can be a powerful architectural concept. However, there can be opportunities to simplify the operation of the system as a whole (and make it more robust) by designing one of those interacting processes to create common shareable resources that can be reused throughout system.

This time out we will consider a couple simple ways to implement this functional reusability without creating a brittle application.

Creating Reusable Resources

So the first thing we need to do is figure out exactly what we mean by “Reusable Resources”. In a sense we are creating a reusable resource every time we build a reusable library of drivers or functions, but that sort of reuse is not what I am talking about here. The distinction is that things like libraries are for really tools for the developer and users seldom even realize they exist. What I am talking about in this post are aspects of the program that are user facing.

For example, say you have an application that has 4 or 5 screens and they all need a couple buttons and a menu or two, but the buttons and menus on each screen do different things. One solution would be to simply duplicate the button and menu logic on each screen. but there are limitations to what you can do in this way. To begin with, forget the idea of having menus. Windows in subpanels technically aren’t open, so they don’t have menu bars and can’t have menus.

A far more flexible solution is to let the main GUI carry these reusable, or shared resources and let the subpanel VIs interact with them via references. Right now, that approach will be the focus of our attention.

Basic Concepts

One of the beautiful aspects of VI Server is that it allows you to do nearly anything with references that you can do directly. Therefore, our basic approach will be to pass references to the shared resources to the subpanel VI when it becomes active (i.e. visible in the subpanel) so it can register for, and respond to, the events associated with those resources. As a demonstration of how this works, we’ll add a button and a very simple menu example. The menu example will be simple because I am planning a complete discussion of menus for later time.

A New Requirement

The application to this point has provided a means for looking at data, or at least simulated data. It does not, however, have the ability to store any data. What we want to add is the ability to press a button, or make a menu selection, and have the data source we are looking at save the current chart contents to a text file. In addition, to raise the bar a bit higher, let’s also include in this requirement that the button and menu have to reconfigure themselves based on which data source is selected. In other words, if we are looking at the sine data source, the button and menu should say something like, “Save Sine Data”, or if viewing the ramp data, “Save Ramp Data”.

So what do we need to do to implement this functionality? To answer that question, let’s consider what we know. We know that a big reason for using subpanels in the first place is to eliminate data transfers. Therefore the data needs to be saved from the acquisition process itself — what would be the point of moving it somewhere else first?

Moreover, if each process is going to be handling its own data, doesn’t it make sense to let it handle the button clicks and menu selections too? The alternative is for the GUI to detect the button click or the menu selection and, in response, generate another event of some kind to let the acquisition processes know what happened. But there is no benefit to having the GUI acting as a middle man, mediating the communications. Better to go with the simple way.

And this is how you do it:

Dynamically Managing Events

If the easy way to handle this requirement is to let the acquisition processes handle the buttons and menus, we are going to need a way to turn events of all kinds on and off — not just timeouts. After all, there are three acquisition processes and we only want one of them at a time managing the shared resources.

To see how that works, let’s revisit for a moment how you register dynamic events. Here is a typical snippet of code (in fact, a subVI named (re)create reuse events.vi) that is registering a couple events. The basic idea is pretty simply. You wire a reference of some kind to the node and LabVIEW will show you the specific events that you can associate with that reference. In this case, we are creating a value change event from a Boolean control reference, and a menu selection event derived from a VI reference.

zombie event builder

By the way, if you plan on building this subVI from scratch, be aware that this situation is one of the few places where LabVIEW cares about the order in which you wire things. To duplicate my results, first create and name the input controls, then wire them to the Register for Events node, finally create the output event refnum indicator, and then duplicate it to create the corresponding input. Once you have the subVI (or at least me implementation of it), this is what it looks like installed into one of the acquisition processes. It’s the one with the red banner across the top.

initializing zombie events

If you are observant you might be wondering how this is going to work because neither of the reference inputs that the subVI uses to create the registration, are connected to anything — which means that the references they are passing to the registration node aren’t valid. The thing is, when LabVIEW creates an event registration it doesn’t check for the references to be valid. Used in this way, the references only have two functions: Define the type of reference associated with the event, and name the event.

This fact is important because it is central to what we are trying to accomplish. As long as the references associated with the registration are invalid, the events are in essence turned off because you can’t fire an invalid event. To turn them on, all you have to do is modify the registration to use valid references. This snippet shows how I have modified the Change Source event and its event handler.

change source with sequence frame

First, I moved the My Name control outside the event loop because I an going to be needing it several places. Second, I modified the event data to pass three references in addition to the name of the source being selected. Two of those references relate to the values we used to initialize the registration. The third reference is a menu reference that we will discuss in a bit. Third, I modified the event handler such that if My Name matches the selector string from the event data, the registration is recreated using the two real references, thus allowing this VI to receive those two events. If My Name does not match the selector string from the event data it executes an instance of the registration VIs that has no references wired to its inputs. Fourth, what in the world is that single frame sequence structure with the wires passing through it for?

In the finished code you will see a subVI sitting there, but I wanted to pass on a tip. Many times when working I will realize that I will need a subVI that I haven’t created yet. To allow me to continue work, I will drop down a single frame sequence structure and wire up the data inputs and outputs that I know it will need. Once that it done, I turn it into a subVI by selecting it and then choosing the Create SubVI option from the window’s Edit menu. As a reminder that I have to go back and finish it, I will leave the new subVI with the default icon.

We’ll get to what this mystery VI does in a bit. But it should be obvious that these changes are useless if there are no error handlers, so I added them in too. Here is what the handler for the button press looks like.

save button key press

We first call our little library function to pop the button back out (since we obviously can’t have the terminal for the button here) and then calls a new subVI to save the data to a text file. This is what the data saving VI looks like on the inside:

data save subVI

Note the comment I put in the code. I will be wanting to come back and reimplement this data saving better so I put a bookmark in the code to remind me.

We are also going to need an event handler for the menu event as well, but before we can build that code, you need to know a bit more about how menus work in LabVIEW.

The 411 on Menus

As I stated before, I am going to devote an entire post to menus in the near future, so the following discusses a very basic implementation.

The first thing you need to know about menus is that to create and manipulate runtime menus you need a menu reference — no surprise there. Unfortunately, LabVIEW doesn’t treat menu references like it does references to other parts of a VI. For example, if you need a reference to VI’s front panel, you can get it from a reference to the VI. Not so with menu references. The only way to get a menu reference is from a special node that has to reside on the block diagram of the VI that will be showing the menu. This “quirk” is why we need to pass two different references to the subpanel VI that both deal with menus: the VI reference allows us to register an event for the menu, but we need the menu reference in order to make changes to the menu contents.

Next, you need to know that every menu item has a Name and a Tag. In many ways these properties are analogous to a control’s Caption and Label. Like a control’s label, a menu item’s tag cannot be changed dynamically because it is the way that LabVIEW uniquely identifies that particular object. However, the menu item name is the visible string you see when you operate the menu and (like a control’s caption) can and often does change during the execution of an application.

Here is the code in the GUI that will define our very basic menu and its three items. Note that I have packaged this logic in a subVI so when I upgrade the menu generation, it will be easier to do. Remember that a good principle for when to make something a subVI is to ask yourself is this functionality (or the implementation of the functionality) likely to change?

building simple menus

Breaking down the logic, the first node deletes all the existing menus. The next node creates a new top level File menu. Finally, the third node is in a loop and it creates a selection to save the data (tagged: Save Data, a dividing line and a Quit selection to stop the application. As I stated before, the tag for each item must be unique. To make meeting this requirement easy, I create hierarchical tags that are a colon delimited listing of the item’s parent tags. For example, the tag for the Quit selection on the File is File:Quit. This structure also makes the tag easy to parse in event handlers for the menu.

The Menu Event Handlers

Given the menu is itself a shared resource, the response to the menu events is also shared. Here is the menu event handler that is in the GUI. It handles the event that related to the application as a whole — Quit. As you would expect, all it does is run the VI that verifies that the user wants to quit before firing the standard Stop Application event.

quit menu handler

Likewise, there is the menu handler that resides in the acquisition processes. It simply calls the same data saving VI we used in the button press event handler.

save data menu handler

In both handlers, menu events for which they are not responsible, are handled by an (empty) default case.

Changing Resource Appearances

Remember the dummy VI we created earlier? Its job is to implement the requirement that the markings on the button and menu change to reflect the process that is managing them. Here is what it looks like:

modifying shared resource appearance

The My Name value is used to generate the new marking which is then applied to the button’s Boolean Text property and a node that uses it to set the menu item’s name. The node uses the item tag File:Save Data to identify the specific item it is changing. If you check out this node using the online help you’ll notice that it also let’s you enable and disable items, and add or remove check marks in front of items.

Code Testing

After making the required modifications to the other two data sources, we are ready to test the code. As you go from one data source to the next you’ll notice that the menu selection and button text change to reflect the name of the source that is being displayed. While you’re at it, test the data saving and see that the application remembers the last place that you saved a file.

This is all on subpanels for now. I have gotten some really good comments from several of you and will rolling some of those questions into future posts. In addition, when you download the code for a post, be sure to go through all of it. There are a lot of things going on in the code that I comment on in the VIs, but don’t have space for in the post.

Testbed Application — Release 10

Toolbox — Release 5

Oh yes, a preview… In the next post I will start looking at another popular design pattern and what a proper LabVIEW implementation of it looks like.

Until next time…

Mike…

Building a Subpanel-Based User Interface

Having discussed some of the main issues related to incorporating modularity into your application’s GUI, we are now ready to look at how to tweak what we have already built into an application that incorporates those ideas. From the user’s standpoint, the program will operate very much as it did before, but with no data transfer hassles: there’s not any data transfers!

Let’s See Them Work!

To see how subpanels work we will modify our existing testbed application. However, these modifications will only require minor changes to the application. Remember, it is not an accident that this sort of modification can be accomplished without a major rework of the code. This sort of adaptability results from a conscious decision to use techniques and methods that are inherently adaptable. Always be thinking ahead…

And if you do think about what we are doing here you realize the required modifications will be minor in scope because there really isn’t very much in the code that fundamentally cares how the interface is structured. In fact, there are only two places that will need to be changed. The GUI needs to know what to do when a different screen is selected — this logic needs to change. Likewise, the acquisition processes (which previously ran unseen in the background) now need to be prettier, but on the whole they will be doing less than they did before.

Making Background Task Presentable

So let’s start by turning the background tasks into something we would want the world to see on our application’s main interface. It only takes a glance to notice that the existing graphs are too small, so the first step in the transformation is to resize the graphs on our three data sources and make them all the same size as the existing graph in the GUI. In addition, because all three processes will be going into the same subpanel, we also need to make sure the front panels of the three acquisition processes are all the same size: slightly larger than the graph. While we are doing cosmetic things, we also want to make sure that the front panels are the same color as the main GUI.

The functional modification needed to make these VIs live happily in a subpanel is to go into their VI properties and in the Window Appearance section, turn off both scrollbars.

window appearance settings

We now turn to the code changes. In order to put a VI into a subpanel you write a reference to the VI to a subpanel control method called Insert VI. The thing is, retaining references to background processes that you launch can get tricky. This point is especially true when you need to launch multiple instances of a reentrant VI. Although the testbed isn’t (currently) using reentrant processes, the software does support it so we need to think about how to get references to the processes that will be going into the subpanel. One simple way to accomplish this goal is to have the processes “publish” a reference to themselves that can be used elsewhere in the code. To implement this approach, I created a buffer that has two public interface VIs. One appends a reference to the buffer contents and one reads the buffer contents. Note that we won’t take the time to look at the code for these functions here because we have examined this structure before.

To use these VIs, we put an instance of the insert VI at the very beginning of the call chain in each of the acquisition processes. Like so:

inserting vi reference

The only other changes we need to make to the acquisition logic both relate to the timeout value for the loop. We no longer need the acquisition to wait before starting so a single keystroke will change the “-1” timeout in the initialization logic back to “1”.

resetting the timeout

Likewise, we don’t need the Change Source event right now, but we may need it again (soon) so we’ll leave the event handler in place, but modify the code so it doesn’t do anything.

change source doing nothing

…and that is all we have to do to the acquisition processes.

Adding the Subpanel to the GUI

With the acquisition processes modified, all we have to left to do is change the front panel control on the GUI, and alter what happens when you make a Data Source selection.

Changing the front panel control consists of simply removing the graph and replacing it with a subpanel. One thing you need to think about is the size that you are going to make the subpanel. I have found through experience that you get a better looking result if you make the subpanel a skosh bigger than the front panel of the VIs that you are putting in it. For the purposes of this discussion a “skosh” is an empirically determined constant equal to 4 pixels, so make the subpanel 4 pixels taller and wider than the acquisition process front panels.

You’ll note that when you place a subpanel on a front panel, LabVIEW also installs an invoke node for it on the block diagram, we’ll use that in a little bit. The only other thing you need to do that is related to the graph is remove the VIs implementing the update UDE, and delete the associated event handler.

Note: Normally I would put a screenshot here illustrating the change, but in this case I can’t think of a good way of showing something which is no longer present, and isn’t being replaced by something…

Concerning the Data Source value change event, this is where we get to use the invoke node that LabVIEW created when you instantiated the subpanel. Remove the VI for firing the UDE, and wire in the invoke node in its place. As you can see the node has one input that is a reference to the VI that is to appear in the subpanel. Now, our technique for getting that reference is remarkably similar to what we did to get the data value for the UDE. The difference is that instead of selecting one element from an array of strings, the same array index node is now selecting one element from an array of VI references — references that come from the read VI for the reference buffer we discussed earlier.

new data source selection operation

Running the Result

If you run the application that we have created, you will notice that (with the exception of a couple points) the operation is very similar to the way it worked in the last release. As you make selections, you can observe that the data displayed still changes as it did before. But if you watch carefully, you will notice that the plugins continue to acquire data even when you aren’t watching them. Moreover, if you go back to a screen after having not looked at it for few seconds, you will notice that it will initially show the previous data and them update all the “missing” datapoints at once. This might seem strange, but it is the side effect of a very good thing that LabVIEW does for you.

Knowing that updating screens that aren’t currently visible can use a lot of computer resources unnecessarily (especially if they have charts on them), LabVIEW keeps track of front panels that are visible and only takes the time to update the data displays if the screen is visible. The delayed update that you see happening all at once is the result of LabVIEW realizing that the front panel is now visible and sending all the updates to the chart at once. On simple controls and indicators, these updates typically happen so fast that you have to really be looking for them, but I wanted you to see the effect.

Note that in the previous paragraph I did not refer to front panels that are “open”. The thing is, not all screens that are open are visible. For example, VI’s running in the background (like the data acquisition processes used to be) are open but Hidden, i.e. not visible. Conversely, the front panels of VIs in subpanels are visible, but they aren’t open. In any case, here’s the updated code.

Testbed application — Release 9

Project Toolbox — Release 4 (No Change)

Before we move on to the next thing I want to cover, I’m going to hang-out with subpanels for one more post. Specifically, designing VIs in subpanels to be completely autonomous processes is a very powerful technique, but sometimes a good way to foster reusability is to have the main GUI provide some standardized, reusable resources that the various subpanel plugins can use. To cover that situation, next time I’ll look at ways to manage shared resources like buttons and menus from subpanels.

Until next time…

Mike…

Modularization – It’s not just for Block Diagrams (Front Panels can play too!)

At the very end of my last post I suggested that perhaps the best way to display user data on your application’s GUI is to avoid sending data to the GUI in the first place. Of course the big question is how the GUI can display data that you don’t send to it?

The programmatic slight of hand that explains this paradox is a feature of LabVIEW called a subpanel. To understand how subpanels fit into the overall LabVIEW “ecosystem”, let’s review for a moment. As anyone who has used LabVIEW for more than 5 minutes will recall, every VI you create has 3 main parts:

  1. The Front Panel — This part provides the VI with a user interface for interacting with the routine.
  2. The Block Diagram — This window is where you put the graphical source code that describes what you are trying to do.
  3. The Connector Pane/Icon — This part allows you to call your VI from the block diagram of another VI.

Now all programming environments have places to enter source code and methods for calling or reusing other code, but the Front Panel is really unique to LabVIEW. With most languages, you have to write code to create the GUI, but with LabVIEW, the GUI is part of the code. Just as calling one VI from the block diagram of another VI is a way to modularize your program’s logic, so subpanels provide a way to modularize your program’s user interface by incorporating the front panel of one VI into the front panel of another VI.

But Why Subpanels?

To be honest, there are other techniques for “modularizing” user interfaces within LabVIEW, and we’ll take a quick look at a couple — though to be honest we should call the result of one technique “pseudo modularity” because while the interface looks modular, it’s really not.

First we want to consider XControls. A couple years ago at NIWeek, I presented a session on how to use XControls, so it’s a topic with which I am familiar. XControls approach the question of GUI modularization by providing a mechanism for creating custom front panels controls that implement custom functionality not found in standard controls. Although they are very powerful, they have some issues:

    Problems with XControls

  • Complexity: XControls are difficult to create and require both greater skill on the part of the developer and advanced knowledge of how LabVIEW works internally. As you can imagine, an XControl of even modest complexity can take a long time to develop. The result is that the functionality they encapsulate must be sufficiently reusable to justify the effort required to create the XControl.
  • Can Complicate Debugging: One of the “interesting” aspects of using an XControl is that they are almost always running. Of course, when you think about it, this makes sense. A control obviously has to exhibit some useful behavior at run time. However, during development they must also be able respond to changes that the developer is making. For an XControl this requirement means that, on some level, it is running as soon as you drop it down on a front panel. While this fact obviously complicates the process of debugging the XControl itself, it can also adversely effect the debugging of the program you are trying to create — especially if the XControl uses some of the same libraries that you use in creating your program. You can see limited access to subVIs because they are being called in the XControl and so are already running, or are at least reserved for execution.
  • Support is Limited: Actually, “stinks on ice” is probably closer to the truth. Documentation is poor, examples are practically nonexistent, and because NI considers it an advanced topic, tech support is often no real help.

Tab Controls — Just Say “No”

This technique is the one I described as “pseudo modularity”. As a control metaphor, the tab is familiar to anyone who has been around computers for a while. However, operating system designers have been moving away from tab interfaces for a variety of reasons. To be fair, I would like to start this section with some of the positive things about tab controls, but to tell the truth I can’t think of any…

    Problems with Tab Controls

  • Complexity: While a tab control might help visually organize the 30 or 40 controls and indicators on your front panel, the block diagram can rapidly become a mess as you try to deal with the functionality associated that many terminals. Moreover, because the tabs hide complexity, they increase the likely that your front panel will have 30 to 40 controls and indicators.
  • No Reusability: Let’s say you create really nice a user interface on one tab of your application. Now let’s further postulate that the interface is so good that another internal customer wants you to build them an application that uses the same interface. How do you do it? Well, with tabs, your option is to cut and paste the controls and then rebuild the logic on the block diagram.

    Of course that’s just the beginning of the challenges. You still have to verify that the code works the same in the new context, and you have to hope that the new program’s timing doesn’t effect how the interface works.

  • Compatibility Problems: It is unfortunately not uncommon to see posts on the forum where someone is wanting to know why common controls and indicators don’t work as expected when they are on tab controls. Such questions are not uncommon because, to be perfectly frank, LabVIEW has a long history of tab controls causing a variety of screen update and performance problems.
  • Confused VI Server Interface: Normally when working with controls and indicators the terminals of which are on your block diagram, finding there references programmatically is easy. With a reference to the VI’s front panel you can get an array of the controls on the front panel, and the reference to the control will be in that array.

    But a tab control complicates the whole process. First you get a reference to the tab control, from that you get an array of references to the pages in the tab control, and once you have found the right page you can get the array of control references containing the control reference you want.

A Good Solution for GUI Modularity

But there’s another way to answer the question: “But Why Subpanels?”: the advantages that subpanels offer. But please note that you can only expect to realize these advantages if you use subpanels properly — which is to say, VIs appearing in the subpanels are autonomous processes that encapsulate the logic for a single operation and, when necessary, display the results of those operation on their own front panels.

    Subpanel Advantages

  • Subpanels Simplify Code, Reduce Memory Footprint and Improve Efficiency: When using subpanels you are able to realize these benefits because the minimal code required implement them is almost always smaller than the code you take out that implemented the redundant communication and display formatting operations. In addition, remember that because you are reducing the data manipulation the code is performing, you are also reducing the memory (and CPU) needed to manage it.
  • Subpanels Make Code Robust: When designing a process VI that will run in a subpanel, it is easier to create highly cohesive code with very low coupling between it and the remainder of the application.
  • Subpanels Generalize GUI Structure: Working with subpanels inherently puts you into a mode where you start thinking about the GUI at a higher level. One side-effect of thinking in this way is that so much application-specific logic is moved into subpanels that, in the end, the GUI itself doesn’t know or care what application it is running.

    This point is particularly interesting because the interface could get so generic that it could be divided up into separate dedicated areas like this:

    model screen - with notes

    Normally the subpanel frames would not be visible, and there would be no annotations in each section, but then the front panel would be basically blank, and very uninteresting to look at. To get an idea of what could go in each section, consider these descriptions. Remember, not all of these sections would necessarily be on every screen, and their sizes can be adjusted to fit requirements.

    Header: The intent of the header subpanel is to show VIs that present information that is unlikely to change as a function of the screen being shown in the body. This area could include things like the current time, the operator who is logged in, total operating hours or the name of the display.

    Footer: This subpanel extends across the entire bottom of the screen. It could hold VIs that provide things like a status display, or a scrolling alarm status list. Like the header, this section ideally wouldn’t change during the course of program execution.

    Body: The application’s main display VIs would go in this subpanel.

    Sidebar: The VIs providing this section’s contents of could include things like navigation buttons, or supplemental information that is related to the main display.

    If you have ever done any web development, this basic layout might look a little familiar, which is appropriate since I “appropriated” the idea. One of the key concepts of the web is to separate content from where and how the content is displayed to the user. This approach in LabVIEW is a (small) step in that direction with the subpanels providing the display structure.

So combine these advantages these with a host of others, and you can begin to get a feel for why I consider subpanels to be among the top 5 features added to LabVIEW — ever.

Let’s See Them Work!

To get our first taste of subpanels in action, we won’t go to the extent of creating a fully abstracted interface like we just saw. Rather, I will show how to manage one subpanel — and you can expand it from there, however this post is already getting long, so we’ll have to wait until next time to look at that code.

Until next time…

Mike…