The State Machine and Queued Message Handler


The State Machine and Queued Message Handler

In Chapter 6, "Controlling Program Execution with Structures," you learned about the power of the While Loop + Case Structure combination. We saw a few permutations of this simple application design pattern. We ended the discussion by providing a scalable solution for handling multiple button pushes.

But there is an even more powerful pattern for combining the While Loop and Case Structure: one that allows one frame of a case structure (inside of a While Loop, of course) to define one or more cases that will be executed on the subsequent iterations of the While Loop. There are a variety of permutations of this pattern, but they are commonly referred to as "state machines" and "queued message handlers."

There are a huge variety of implementations of state machine patterns. Fortunately, LabVIEW comes with some very good example templates that you can use in your applications. Just launch the File>>New . . . dialog to find thesedo that now, and we'll guide you through two very useful example templates.

The Standard State Machine

If you haven't already, open the File>>New . . . dialog from the menu. Browse to VI>>From Template>>Frameworks>>Design Patterns, select Standard State Machine, and press OK. This will open an untitled VI with a block diagram that looks like the one shown in Figure 13.70. (Actually, yours will look larger, as we have resized some of the block diagram objects so that they would fit compactly on this page.)

Figure 13.70. Block diagram of the Standard State Machine template


There are some key concepts related to this state machine template:

  1. A shift register is used to store the state variable, which is simply a value that determines which frame of the state machine will execute.

  2. The state variable data type is an enum that has a type definition. (Type definitions were discussed in the last section of this chapter.) Every enum constant you see on the block diagram is an instance of the type definition. You cannot edit the enum constants. You must edit the type definition in the Control Editor window, which may be opened by selecting Open Type Def. from the pop-up menu of any of the enum constants. This ensures that all the enum constants' elements are identical, so that you don't have to edit each of them manually when making changes. (Note that when you attempt to save the new untitled state machine VI, LabVIEW will ask that you first save the enum type definition (.ctl) file.)

  3. Because the state variable is an enum and is wired to the case selector terminal of the Case Structure, the case names are strings that are validated by the LabVIEW editor. There is no chance of misspelling a case nameif you do so, the VI's run button will appear broken and you will not be able to run the VI.

  4. The conditional terminal of the While Loop is wired to a comparison function that evaluates whether the state variable is equal to "Stop." If so, the While Loop terminates, and the state machine stops.

  5. Each frame of the case structure must pass out a value for the state variable, which is wired to the shift register, called the state variable shift register. This allows each case to define which case will execute on the next iteration. (Except, the "Stop" case does not get to decide which case executes on the next iteration, because the While Loop will terminate on the same iteration as the "Stop" case. Because each case (or state) can only define the state on the next iteration (it cannot, for example, "queue up" more than one state to execute), the LabVIEW Standard State Machine template is technically called a Finite State Machine.

  6. The state variable shift register must be initialized outside of the loop to define the initial state of the state machine. Otherwise, the state machine will have a memory and always execute the "Stop" case after the first time it is calledthat would be bad.

We recommend having no Default case in your Case Structure. Because the state variable data type is an enum, the Case Structure does not need a Default framethere are a small, finite number of possible state variable values. Having a Default frame just means that you might forget to add a new case to the Case Structure after you add a new element to the state variable enum. If you don't have a Default frame, your VI will be broken if there is not a case to handle each element of the enum. If you leave the "Initialize" case as the Default case, then the "Initialize" frame will execute if the state variable value is not handled by a casethat's not the desired behavior.


Now you'll do an activity that uses the State Machine template to do something useful.

Activity 13-9: Using the Standard State Machine

In this activity you will create a state machine from the Standard State Machine template that ships with LabVIEW.

1.

Open a new VI, created from the Standard State Machine template. Do this by selecting File>>New . . . from the menu, browse to VI>>From Template>>Frameworks>>Design Patterns in the resulting dialog, selecting Standard State Machine, and pressing OK.

2.

Save the VI as State Machine.vi and, when prompted, save the StateMachineStates enum type definition as State Machine States Enum.ctl.

3.

Your front panel should look like the one shown in Figure 13.71. Ensure that each of the Buttons are configured for Mechanical Action>>Latch When Released so that they rebound to FALSE after their terminals are read on the block diagram.

Figure 13.71. Front panel of the VI you will create during this activity


4.

Your block diagram should initially look like the one in Figure 13.72.



Figure 13.72. Block diagram of the VI you will create during this activity, in its initial state


5.

Pop up on the Case Structure and select Remove Default so that the Case Structure has no Default (catch all) frame. With an enum-type Case Structure, a Default frame is unnecessary and a liability. We would rather have a broken VI, than execute the default frame by accident, if we accidentally did not have a case for every possible enum value.

6.

Edit the state variable enum type definition, so that it has the following items:

  • Initialize

  • Stop

  • Check for UI Events

  • Acquire Data

  • Analyze Data

Ensure that the enum item names are in the same order shown above.

7.

In the "Initialize" case, change the state variable enum constant's value from "Stop" to "Check for UI Events." (This constant is a type definition that you will need to edit, as you just learned in the "Type Definitions" section of this chapter.) This will cause the state machine to execute the "Check for UI Events" case immediately after initialization. (Note we will be coming back to the "Initialize" case later to add some code that initializes our application.)

8.

Make the "Stop" case visible and then pop up on the Case Structure and select Add Case After. The new case should be automatically named "Check for UI Events." If it is not, use the Labeling tool to rename it. Note that LabVIEW auto-completes the case name, as shown in Figure 13.73.

Figure 13.73. LabVIEW's auto-completion of case names as you type them into the Case Structure


9.

In the "Check for UI Events" case, place an Event Structure, as shown in Figure 13.74. Remove the "Timeout" event case. The Event Structure should have the following event cases:

  • "Acquire Data": Value Change

  • "Analyze Data": Value Change

  • "Stop": Value Change

In each of these event cases, place the respective (like named) Buttons.

Figure 13.74. Your VI's block diagram after adding the "Check for UI Events" case


Again, ensure that each of the Buttons are configured for Mechanical Action>>Latch When Released so that they rebound to FALSE after their terminals are read on the block diagram.

10.

In each of the event cases, place an instance of the state variable enum as a constant. Its value should correspond to the name of the button press. For example, the "Stop": Value Change event case should output an enum value of "Stop," the "Analyze Data": Value Change event case should output an enum value of "Analyze Data," and so on.

The block diagram should now look similar to the one shown in Figure 13.74. Note that the time has been removed. It is not necessary because the event structure will wait patiently for any front panel events to occur.

11.

Pop up on the output tunnel (where the state variable enum is passed out) and uncheck the "Use Default If Unwired" option. This is a huge liability, if we should forget to wire the state variable out of one of the event cases. We do not want it to reinitialize the state machine (which it would, because "Initialize" is the 0th [and therefore default] item of the enum). With this option turned OFF, we will get a broken VI, telling us that we forgot to wire the state variable enum out of an event case, should we forget.

12.

Add a case to the Case Structure for "Acquire Data," as shown in Figure 13.75. Note that the state variable that is output from this case is "Analyze Data." This is a perfect example of the power of the state machine. The Acquire Data case will execute next, before it subsequently goes back to the "Check for UI Events" case. This is the power of the state machine pattern.

Figure 13.75. Your VI's block diagram after adding the "Acquire Data" case


13.

Add a case to the Case Structure for "Analyze Data," as shown in Figure 13.76. You will need to read the data from the waveform graph using a local variable in read mode.

Figure 13.76. Your VI's block diagram after adding the "Analyze Data" case


14.

Modify the "Initialize" case, adding code that writes initial data to the local variables of the waveform graph and signal measurements, as shown in Figure 13.77. Note that the array constant is an empty array. You can ensure that it is empty, by selecting Data Operations>>Empty Array from its pop-up menu.

Figure 13.77. Your VI's block diagram after adding the "Initialize" case


Your front panel should now look similar to Figure 13.78.

Figure 13.78. Your VI's front panel after completing this activity


15.

Save your VI as State Machine.vi. You are now ready to run it.

Run your VI. Press the acquire button and note that the data are displayed in the waveform graph and the analysis parameters are also updated (remember, each time you acquire data, the analysis will also be performed).

Change the analysis method from "peak" to "histogram," and then press the Analyze Data button. Notice how the measured values change.

Change the amplitude value, and then press the Acquire button again. Note that the amplitude of the signal on the graph changes, as do the analysis results.

Press the Stop button, and then run your VI again. Note that the graph and analysis results are initialized to the values you specified in the "Initialize" case.

Congratulationsyou've just made a very powerful state machine.

The Queued Message Handler

Open the File>>New . . . dialog from the menu. Browse to VI>>From Template>> Frameworks>>Design Patterns, select Queued Message Handler, and press OK. This will open an untitled VI with a block diagram that looks like the one shown in Figure 13.79. (Actually, yours will look larger, as we have resized some of the block diagram objects so that they would fit compactly on this page.)

Figure 13.79. Block diagram of the Queued Message Handler template


There are some key concepts related to this Queued Message Handler template:

  1. A shift register is used to store the message queue, which is an array of message strings.

  2. Each iteration of the While Loop, the Delete From Array function "pops" the last element off of the array (from the end of the array, because the index terminal of Delete From Array is unwired). This makes it a Last In First Out (LIFO) queue.

    We do not like the LIFO queue behavior. We prefer to have the queue elements removed from the front of the queue, making it a First In First Out (FIFO) queue. Making this change is easyjust wire a zero (0) constant to the index terminal of the index input terminal of Delete From Array.


  3. The message queue data type is an array of message strings. Each message string corresponds to a case of the Case Structure. The messages in the message queue are executed by the queued message handler in sequence (starting from the last element of the array and working up to the 0th element).

  4. Because the message element type is a string and is wired to the case selector terminal of the Case Structure, the case names are strings that are not validated by the LabVIEW editor. There is a very good chance of misspelling a case nameif you do so, the VI will still run.

    We suggest that you make the Default frame an error-generating frame that displays a message (or generates an error) when an unhandled message is passed into the Case Structure. This will alert you to the bug in your code, and help you fix it.


  5. The conditional terminal of the While Loop is wired to a comparison function that evaluates whether the message string is equal to "Exit." If so, the While Loop terminates, and the queued message handler stops.

  6. Each frame of the case structure must pass out the message queue, which is wired to the shift register, called the message queue shift register. This allows each case to modify the message queue, either adding or removing message elements to the array. These elements may be added to the front or back of the queue, using the Build Array function.

  7. The message queue shift register must be initialized outside of the loop to define the initial messages on the queue of the queued message handler. Otherwise the queued message handler will have a memory and always execute the "Exit" case after the first time it is calledthat would be bad. Generally, the message queue is initialized with a single array element whose value is "Initialize."

As you can imagine the queued message handler (QMH) is orders of magnitude more powerful than the standard state machine (SSM), because every case has full control of the entire message queue. Remember that the SSM cases can only define the next case that will execute. The QMH is naturally more finicky, due to the loose typing of the message element's string data type. This can be improved by converting the message element into a type definition enum, similar to the one used by the SSM. Finally, it should be mentioned that with the extreme flexibility of allowing each case to have full control of the message queue can make debugging a challenge. It can also reduce the determinism of your code. We believe that artificial intelligence could be implemented using a QMH . . . they can sometimes have a mind of their own. :-)




LabVIEW for Everyone. Graphical Programming Made Easy and Fun
LabVIEW for Everyone: Graphical Programming Made Easy and Fun (3rd Edition)
ISBN: 0131856723
EAN: 2147483647
Year: 2006
Pages: 294

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net