This time we're doing what the boss calls the simplest real example of a Hierarchical State Machinehis abstract oven. He describes it in detail in the document Implementing Non-Trivial Event-Driven Applications in LabVIEW with Active Objects Based on a Universal Communicating Hierarchical State Machine Template, included in the download package, starting on page 3:
Let's consider an abstract oven. At the highest level of abstraction (lowest level of detail) it can be considered to be “On” or “Off”. However, it is a dual mode oven: when “On” it can be either “Baking” or “Grilling”. Naturally, we want the oven to turn the heater on whenever it goes into the “On” state and turn it off whenever the oven is not on. Well, every oven has a door. We will want to turn the light on whenever we open the door and turn it off whenever we close it. (The door doesn't have a window, so we don't need the light when it's closed.) If we open the door while the oven is “On” and then close the door we expect the oven to return to its business, whatever it had been before we opened the door, i.e. either “Baking” or “Grilling”. This functionality requires introduction of one more state, “Paused”, which will be positioned on the same hierarchy level as “On” and “Off”. We definitely don't want the oven to turn on if the door is opened, so we have to break the “Off” state into “Off with Closed Door” and “Off with Opened Door”. The events that need to be processed (in addition to the ubiquitous “Exit Requested”, which is present in any application): “Stop button pushed”, “Bake button pushed”, “Grill button pushed”, “Door opened”, “Door closed”. Actions: “Turn the heater on”, “Turn the heater off”, “Turn the light On”, “Turn the light off”, “Close Application”.
His statechart/HSM is shown in Figure 1.
As was the case with Project 3, his implementation (with a few extra bells and whistles) is included in the Examples folder.
What other information do we need? Just a list of controls and indicators. We'll need buttons to Start Baking, Start Grilling, Stop Cooking, and control the Door. The first three work just like the EXIT button so, as you well know by now, we'll be replicating the Exit Button: Value Changed event case to create them. The fourth button is a switch-when-pressed type, so it will be a little different, but I'll still use case duplication to create itwe'll just have to do more editing than usual! We need the oven light and an indicator to show that the oven is heating.
Define the Files
If you have not done Project 1 using LabHSM version 1.1, please at least read the
Getting Started section now. If you started with version 1.1, please pardon the enthusiasm of this tutorialit was the first one done with 1.1.
Create a new HSM VI either from the LabHSM - HSM Template.vit or by dropping LabHSM - HSM Template.vi on a blank VI.
Save this VI as Demo_Oven.vi in a convenient place.
Open the LabHSM Editor... from the Tools menu.
Click the NEW button.
SAVE the HSM file as Demo_Oven in the same folder as the VI.
Define the Events
Open the Events List editor by button or from the menu
Use Add New After to create the Stop Button Pushed event.
Use Add Copy After to start creating the Bake Button Pushed event to save typing and reduce typing errors.
Double-click the "Stop" in the pop-up window (Figure 2) and type "Bake" in its place, then hit return or click OK.
Similarly, use Add Copy After and Add New After to finish the list of events (Figure 3).
Close the Events List Editor.
To take full advantage of the ability to duplicate actions, we need the front panel controls that generate the various events, so let's implement them next. You already know I'll be using the "Duplicate Event Case..." pop-up a lot.
Open the Demo_Oven block diagram and find the User Events Loop.
Select the "Exit Button": Value Change event case.
Pop-up on it.
Choose Duplicate Event Case....
Select "Exit Button 2": Value Change as the event handled by this case.
Change the label of the terminal in this new case to "Stop Button".
Change the event to be posted to "Stop Button Pushed".
To get the event spelling correct (I'm always chasing misspellings), select it in the event list editor, click Rename, select the entire event name, copy it, hit Cancel, and paste the clipboard contents into the block diagram string.
Change the text of the corresponding front panel button to "STOP".
Duplicate the Stop Button case.
Change the label to "Bake Button".
Change the event to be posted to "Bake Button Pushed".
Change the button text color to black (while the old text is highlighted)
Change its text to "BAKE".
Duplicate this case, in turn, to make the "GRILL" button, already with the correct text color. (Am I lazy, or what?) Well, you do still have to update the button and event text.
Duplicate the Grill Button case to make the Door switch, as above.
Replace the button with a slide switch.
Make the boolean text visible again.
Change it to "Closed" for the OFF case and "Opened" for the ON case.
Move it off to the left of the switch (Figure 4).
Change the mechanical action to switch when pressed, so that the door remains opened when you open it.
While you're on the panel,
Add the Light and Heater indicators;
Drag up the State indicator;
Align things neatly (Figure 4.)
Figure 4: Demo_Oven.vi Front Panel
The block diagram now probably has our two new indicators sprinkled about the Door event case. Pull them out onto the main part of the diagram near the lower right of the event structure, where they'll be handy. (The Door switch doesn't really need to be read within the event case, as the other switches do, but it doesn't get read anywhere else either, so I'd leave it there for consistency.)
|All the switches except the Door switch are momentarythey produce only a single type of transition and a single type of event. The Door switch moves from Closed to Opened and then later from Opened to Closed causing two different events. On the block diagram, we need to
Duplicate the event string constant;
Populate the two constants with the appropriate event names;
Select between them based on the new value of the switch (Figure 5).
(We could have read the switch to get this value but, under pathological conditions, if someone unchecked the lock front panel box, the switch might not still have the value it acquired to cause the LabVIEW event. The value on the NewVal node will always be the one that caused the event. It's much safer to read this node rather than the switch.)
Figure 5: Choose from Two Events
Define the Actions
Now for the real fun!
Position the screen so that you can clearly see the case label in the Actions Case Structure.
Switch to the LabHSM Editor and open the Actions List Editor.
Push the LabHSM Editor mostly off screen and position the Actions List Editor as in Figure 6.
Highlight the "Run State Machine" action,
Click the Add New After button.
Fill in "Turn Heater On" as in Figure 6.
Now, click the OK button.
"Whamo!" The screen flashes and you have a new action case (Figure 7).
Figure 6: Before
Figure 7: After
If you look closely, you'll find that this case is not empty. The FSM Frequently Used Data cluster is wired across the bottom of the case. When you ask for a New action, you get a copy of the Do Nothing action. In fact, although the action itself has been added where we asked, you'll find the case listed immediately after the Do Nothing case. To avoid confusion, I'd suggest reordering the cases using the pop-up Rearrange...>>Sort.
Now add the code to actually turn the heater on.
Pop-up on the Heater indicator terminal. (See why we put it where we did.)
Create a local variable.
Drag this local variable into the new Turn On Heater action case we just created.
Pop-up and create a constant.
Click the constant to the TRUE state. All done.
Replicate this action three times using Add Copy After "Turn Heater Off", Add Copy After "Turn Light Off", and Add Copy Before "Turn Light On", to minimize typing. (Laaaazy!)
If we had used the indicator instead of a local variable, we would have created three new indicators in the process. In fact, we really should have created only one indicator in the first place and used this operation to duplicate it as in
The Add Copy ... buttons do not whisk you to the new action casesyou're still in the Turn Heater On case.
Step through the cases, clicking the constants to the correct state and using the pop-up Select Item... to switch the appropriate locals to the Light indicator. If you're a purist, substitute the real indicators for two of the locals. Your action cases should look like Figures 8 through 11.
Save the VI and the HSM file.
Heater On Action
Heater Off Action
Light On Action
Light Off Action
Define the States
Okay, the boss wants a real state machine this time, so we've still got some work to do. Open the LabHSM Editor and the state chart. We see from the chart that the top state has three substates: Off, On, and Paused. The heavy curved arrow near the left border of the Top State (and OSHA regulations ) indicates that Off should be the default. (I find it helpful to highlight on a paper copy each feature of the state chart as I implement it, in order to track what has been done and what remains to be done. You could mark up a PDF file instead, but I find it difficult to draw neatly with a mouse.)
Select the current default Ready/Idle state.
Rename it to Off so that the oven starts in the off condition.
Choose Add New Substate to create the Off with Closed Door state.
Since this is the first substate created, it will show as being the default, which is correct. If it had not come up as the default, we could have set it so with the Set As Default Substate button.
Reselect the Off state and use Add New Substate to create the Off with Opened Door state (Figure 12).
Use Add New Substate on Top State to create the On state.
Use it twice on the On state to create Baking and Grilling.
Set Baking as the default.
Use Add New Substate on Top State again to create the Paused state.
The Error state can be dragged below Paused to get the arrangement in Figure 13, if you like. (If you have trouble rearranging states without undesired demotions, collapse the higher state above the destination location to hide its substates before trying to make a move.
Figure 12: Oven Off States
Figure 13: All Oven States
Assign Events, Actions, and Transitions
The Upper left corner of the statechart shows the events that are handled and the actions called for directly in the Top State. There is no entry action and the only exit action is the built-in Close App. Note that the statechart calls for the Door Opened and Door Closed events to be handled here. Lets add them.
Select the Top State in the left hand window and verify that transition 0 (in the right hand window) is the correct, default, terminating type.
Click the indexing control to select transition 1.
Choose the Door Opened event to trigger the Turn Light On action.
Similarly, set transition 2 to activate on the Door Closed event and trigger the Turn Light Off action (Figure 14).
Figure 14: Top State Transition 2
The Off state has no transitions itselfthey're all assigned to its substates.
Off with Door Closed must respond to the Door Opened, Bake Button Pushed, and Grill Button Pushed events, but with no actions, only transitions to other states.
Set those transitions in that order. The baking transition is shown in Figure 15. The others are equivalent.
Figure 15: Off to Baking Transition
When the oven is off and the door is opened, it ignores everything but the Door Closed event, which causes a transition to the Off with Closed Door state.
Set up that transition.
Moving down the tree, the On state has an entry and an exit action, and two transitions: to Off with Closed Door and to Paused. (Figure 16 shows one transition.)
Figure 16: On State Transitions
Baking and Grilling have no actions or transitions, so they're very easy.
Paused has but a trivial transition to Off with Opened Door triggered by the Stop Button Pushed event and a much more interesting transition for the Door Closed event.
Do the trivial and then we'll discuss the second.
If we just select the Door Closed event with the On state as the target, we get a situation as in Figure 17 (which shows only the right side of the panel). There are two things wrong with this panel. First note that, when we go to the On state, we will always go to the Baking state, since it is the default, even if we were originally grilling. To correct this, we need to select To History as the Transition Type, as shown in Figure 18 and as indicated by the circled H on the statechart. With this correction, the On state will resume where it left off. But the light will stay on! The light is turned off and on in the Top Level state, so that we don't have to keep track of it everywhere. The switch in the lower left of Figure 17 says that this particular response to the Door Closed event should replace the response (Turn Light Off) of the Top State. We really want both the response called for here and the response called for at the higher level. Disable this option here (as in Figure 18).
Set the On state as the target of the Door Closed event .
Select To History as the Transition Type.
Disable Override Supersates' Actions.
Figure 17: Incorrect Transition
Figure 18: Correct Transition
Save the VI (if necessary) and the HSM file.
Test the Pudding
Run the VI. Push the BAKE button and the Heater comes on. Push STOP and the Heater goes off. Push GRILL and the Heater comes on. Push BAKE and nothing happens. The oven keeps grilling. Just like it should (according to the statechart). Open the Door and the Heater goes off ... but the Light doesn't come on! Close the Door and grilling resumes. Everything works as advertised except the Light.
Debugging time! Put a breakpoint on the wire between the constant and the local in the Turn on Light action case. Run the VI, open and close the Door, and we never get to the breakpoint. Put a breakpoint in the "Door": Value Change (LabVIEW) event case. Gee, we get there okay! We're posting the event, but not responding to it. Maybe it's one of my famous misspellings. No, copy and paste doesn't fix it! It looks like the event is being overridden. Wait a minute, we just killed an override. Are there others?
Well, you could poke around and find them but, if you're running the licensed code, you can use the Usage button on the Events List Editor to find every place that uses the Door Opened event (Figure 19, where I've juggled things a bit to compact the picture). (If you're running the demo version, the Usage button is grayed out, as in Figure 2, and disabled. Note that the Actions List Editor, Figure 6, has a similar capability.) There's even a Go To button that moves you right to the places where you can fix those little override buttons in the wrong state. Override doesn't matter in Top State because there's nothing higher to override, but both those other states are problems. I bet there's the same problem with Door Closed.
Figure 19: Usage Finds All Door Opened Events
Click on the Door Opened pull-down menu in the Events Usage window and you can select any other event, including Door Closed. You'll find that we've got one right (the Paused state we set explicitly above), one wrong (Off with Opened Door), and one don't care (Top State).
Save the files and run the VI and watch it work as designed.
Paul F. Sullivan