We should consider the inherent requirements for all components.
All of the components, public functions, and data should be accessible via a simple interface.
We should be able to add/delete/modify our component's actions simply.
Any modifications should have little effect on the overall software design.
The component stores its own state locally and persistently.
The component should initialize itself.
Errors are handled by the component.
Inputs and Outputs should verify themselves.
Message sending is the means of controlling your component. The calling component will send a message to the called component telling it what to do.
There are various methods you could employ to send messages to your components, but we use enumerated types that are simple and self-documenting. In fact, the biggest bonus when using enumerated types is that you can drop your component on your block diagram, right-click on the input, and pop up a complete reference of the components' actions and attributes.
The LabVIEW online guide gives a comprehensive overview, however, for those too lazy here's a summary:
An enumerated type control can be thought of as a drop-down box
Enter additional items by using the labeling tool and pressing Shift-Enter
The enumerated type data type is U8, U16, U32, selectable from the Representation palette. The terms U8, U16, and U32 refer to the number of elements the enumerated type can hold (U8 = 256, U16 = 65,536 and U32 = lots and lots)
Wiring to a Case Structure makes the case display a string rather than a number
Arithmetic operations (except Increment and Decrement) treat enumerated type as their number representation
Increment and Decrement rotate at start and end
A number is converted to the closest enumeration; out-of-range numbers are set to the last enumeration
Well seven actually.
Converts the selected enumeration to its text representation (in the example shown in Figure 5.1 the output string would read “Error State”).
This is useful for storing states in a file or database.
Converts the input string to its representative output. In the example shown in Figure 5.2 the output enumeration would be set to “Test 1”, if there is a corresponding list item, or “Error State” if not.
This is useful for retrieving states from a file or database.
The example in Figure 5.3 converts the number to its enumerated type without making ugly coercion dots (Output String 2 would be set to “Test 2” in this case).
Figure 5.4 shows an enumerated type being cast to or from a string. This is very useful for sending commands through ports. For example, a real-time system could use it as a method of sending commands to and from a PC client to the real-time server using the Transmission Control Protocol/Internet Protocol (TCP/IP) functions.
Gives similar multiple selection functionality to a Select-Case statement in Pascal and Basic, or a Switch in C and Java. Using an enumerated type as in Figure 5.5 improves a system's readability and helps with self-documentation.
By putting the enumerated type in an array and allowing a For Loop structure to auto-index, a sequence of events can be driven. In the example shown in Figure 5.6 they go in sequence, but any order could be selected. From this simple structure a wealth of opportunities arise. For example, the array could be generated from a database or file, allowing a completely configurable system in very few steps.
Wiring an enumerated type into a While Loop shift register makes an extremely important structure called a state machine. The example shown in Figure 5.7 emulates a Sequence structure, but with added flexibility.
Consider the following. You've designed your component and created an enumerated type as a control and wired it to the connector. It's now deployed throughout your software. You now need to add some functionality to this component. You pop up and add a command to the enumerated type. If you put this new command anywhere but at the end of the list you will find all your popped up constants broken. If you put the new command at the end you will find ugly coercion dots everywhere. This is aggravating to say the least. The kindly people at National Instruments have given us a solution—Strict Type Definitions.
You can edit your enumerated type on the Front Panel (highlight control and select Edit>>Customize Control . . ., or set your preferences to “edit a control on a double-click”).
When the Panel shown in Figure 5.8 is displayed, select Strict Type Def. from the drop-down menu and then save in a Controls directory as controlName.ctl.
Now whenever you create a constant by popping up, it will be an instance of the Strict Type Definition. If you change one you change them all. Here are a few things to be aware of when dealing with Strict Type Definitions.
Bad Things
If you create a subVI by selecting it and using the Create subVI, the new Front Panel objects are not linked to the Strict Type Definition.
Similarly, popping up on a tunnel in a Case Structure does not create a tunnel.
And popping up from any of the built-in functions (Scan From String, Unflatten From String, etc.) doesn't create them either.
Good Things
Duplicating Cases and Sequences does preserve the link to the Strict Type Definitions.
Cutting and Pasting also preserves the link to the Strict Type Definitions.
Prior to LabVIEW 6 you didn't get a Strings[] attribute for a Strict Type Defined Enumerated Type, but you did for a normal enumerated type. This was overcome by looping around each element in the enumeration and creating the element content array from that loop.
We were a bit worried about the robustness of using Strict Type Definitions, for example, if we changed one how would this ripple through our software. In practice they have proven to be remarkably immune to change, invariably coping with anything that we could throw at them. Everything discussed here about Strict Type Definitions is also applicable to normal Type Definitions.
LabVIEW has the tools to define a flexible command and attribute message sending mechanism. Oh so powerful! Oh so underutilized!
We want our component to keep ahold of its own state after it has been active. This data should be considered private, hidden from outside of the component and only accessible by the commands sent to the component.
There is only one efficient private method of storing data within a VI in LabVIEW and this is by using shift registers on a For Loop or While Loop structure. Figure 5.9 illustrates their use.
Shift registers are local variables that are primarily used for transferring values from one iteration of the loop to another. They have an important characteristic that we use, and that is persistence. By persistence we mean that the data held within the shift register is kept even after a VI has been run. This gives our components independence, which is essential for loose coupling, and data privacy, which is essential for information hiding.
Let's put it all together and make a dummy component structure.
As you progress through the book you'll notice a common look and feel to all the components. We'll now build a template from scratch for a simple component structure and explain the parts.
One of the simplest structures would be an enumerated type wired to Case Structure. We'll wire the enumerated type through the shift register of a While Loop. This will allow us to write a bit of self-initializing code. Figure 5.10 illustrates this.
The component should initialize itself.
The structure of the component allows it to auto-initialize using the very handy “First Call?” function found in the Advanced>>Synchronization Palette. The “First Call?” function is the small, round effort on the left of the While Loop. The first time a subVI is called from another VI it returns a TRUE, which causes the Initialize element of the enumerated type to be passed to the Case Structure. In the Initialize case would be file loads, array size setting, instrument setup, or any other Initialization duties you can think of. From then on the “First Call?” function passes FALSE. Be aware that if you run the Basic Component VI from its Front Panel it will always return a “First Call?” of TRUE. The Initialize case then passes the Command enumerated type to the shift register and iterates another time.
If your component doesn't need initializing, just leave that particular command out.
Errors are handled by the component.
Inputs and Outputs should verify themselves.
As seen in Figure 5.11, for this template all the cases either Finish and exit or just exit. The UnderRange and OverRange Cases protect the bounds of the enumerated type, and they should be used to throw an error complaining that they've been called. For this example the error-trapping consists purely of passing the errors through. There is no reason that the error conditions shouldn't be separate cases or that part of the Finish case has the capability to check for errors and act accordingly.
The Finish case could be used for postcondition checking, and you could even employ a Start case to check for preconditions. Pre- and postconditions are described in detail in Chapter 6 on complementary techniques.
We should be able to add/delete/modify our component's actions simply.
Any modifications should have little effect on the overall software design.
To add new functionality to the component simply add the function name to the enumerated type and duplicate or add a new case to the Case Structure. All you need to do then is add the new actions. When deleting functionality it is worth removing the obsolete cases before removing the elements from the enumerated type.
All of the components, public functions, and data should be accessible via a simple interface.
The public interface to the component is shown in Figure 5.12, and you must agree that it is very simple. This component is obviously very simple because it has no real inputs and outputs. That aside, if you want to access any of the functions of the component all you need to do is right-click on the Command input, and a Strictly Typed enumerated constant will be created, listing all of the functionality available for the component.
The component stores its own state locally and persistently.
Our example only uses its shift registers to contain data when it is running; it has no need to store any local data between calls. If it did, as does the stack-queue component described later in the book, then you would see one or more shift registers initialized by the Initialize case.
18.220.90.54