From Friday, April 19th (11:00 PM CDT) through Saturday, April 20th (2:00 PM CDT), 2024, ni.com will undergo system upgrades that may result in temporary service interruption.

We appreciate your patience as we improve our online experience.

LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

State machine with multiple event structures

Hi all,

 

I've been working on a large application that performs the familar tasks of providing a GUI for test initialization, automated test execution, and a GUI for interacting with the test as it is being executed.  My basic archetecture is a state machine, but in an effort to improve GUI response I've implemented event structures.  But since I have essentially two separate applications sharing the same front panel (initialization and test), how can I make sure that both sides have stable, fast responses to user actions?  The attached VI is a sketch of what I've come up with.

 

Basically I have two event structures in different states of the main state machine, each handling events from one side of a front panel that has been divided by a vertical splitter bar.  In order to prevent the front panel from locking, I disable the controls on the "inactive" pane (ie: the initialization pane when the VI is executing the test state).  This prevents events from occuring when they can't be handled by the associated event structure.  In the example there is a button on each side that switches from one pane to the other, alerting the user by illumnating an indicator lamp on the active side.  The real program handles the switch automatically, but as long as all the controls for each task are on their own pane, any switching logic could be implemented. 

 

Anyone see any pitfalls with this idea or better methods to accomplish the same thing?

CLAD
0 Kudos
Message 1 of 12
(7,620 Views)

You should probably consider separating your UI processing from the processing tasks. This can be accomplished using a Prodcuer/Consumer model for your code. Essentially, place all of your UI tasks in a single while loop with an event structure. Use your state machine to process you data and run the tests. When you need to interact between the two pass messages via a queue. You can pass events to the UI tasks via user events but I find using queues is more generic. If you need the UI task to process data from a queue you can dequeue messages from the queue in the timeout event.

 

It is always a good idea to separate your UI from the processing tasks. It will allow you to make changes to the user interface (such as customizations for different customers) without impacting your data processing task.



Mark Yedinak
Certified LabVIEW Architect
LabVIEW Champion

"Does anyone know where the love of God goes when the waves turn the minutes to hours?"
Wreck of the Edmund Fitzgerald - Gordon Lightfoot
0 Kudos
Message 2 of 12
(7,614 Views)

Mark,

 

I tried using queues and a producer/consumer layout for this but ran into some issues, namely that messages are occasionally dropped.  I've attached a sketch of the same functionality accomplished as described.  Note that you must press the "Toggle light" button twice in order to change the state of the indicator in the top frame.  I certainly see the advantages to that layout in terms of maintainablity and I like that it sort of "decouples" the event handling from the processing associated with that event, but I can't use it if it drops messages.  Any insight into why the attached code does that?

 

Thanks.

CLAD
0 Kudos
Message 3 of 12
(7,607 Views)

You have to push the light button twice because you do not understand how the events work. You need to register the event to each loop. You can not have one event registered to both loops. If you do this the first loop that gets there exicutes the event and clears out the queue. It is like a race condition. Register the event whit each loop. A seperate instance for each. This should fix your problem.

Tim
GHSP
0 Kudos
Message 4 of 12
(7,598 Views)

The problem that you have is that your dequeues are located in teh case statement. The dequeue should be located outside the case statement and the data retrieved from the queue should determine which state you execute.

 

Your proposed solution in the original post will not solve your UI responsiveness issues since you can get stuck processing long tasks in the event structure. Therefore you cannot service the events until the processing is completed. The only way to exit your application is when you are in event 1. Once in event 2 you cannot exit since you don't have anything looking for the stop event.



Mark Yedinak
Certified LabVIEW Architect
LabVIEW Champion

"Does anyone know where the love of God goes when the waves turn the minutes to hours?"
Wreck of the Edmund Fitzgerald - Gordon Lightfoot
0 Kudos
Message 5 of 12
(7,596 Views)

 


@aeastet wrote:

You have to push the light button twice because you do not understand how the events work. You need to register the event to each loop. You can not have one event registered to both loops. If you do this the first loop that gets there exicutes the event and clears out the queue. It is like a race condition. Register the event whit each loop. A seperate instance for each. This should fix your problem.


While this might solve the immediate issue for this application the important thing to understand is that from a program architecture perspective it is always a good idea to handle the UI separately from the processing. Once you go down the road of combining them all in a single thread you end up having to add a lot of extra processing to keep keep everything in sync and changing the UI can become difficult since it is implemented and spread throughout the entire application.

 



Mark Yedinak
Certified LabVIEW Architect
LabVIEW Champion

"Does anyone know where the love of God goes when the waves turn the minutes to hours?"
Wreck of the Edmund Fitzgerald - Gordon Lightfoot
0 Kudos
Message 6 of 12
(7,593 Views)

@aeastet:  I'm not sure which example you're referring to, but if I handle events from every control on the FP in both loops, the FP will lock.  There is only one event structure in the second example

 

@Mark:  First, let me clarify that by "responsiveness" I'm referring to responses to a button press associated with the task being performed by the program at the time.  I'm not interested in being able to stop the whole program when the "run test" state is active, as that would cause problems for the DUT, only in stopping the run test state.  Really, as long as I don't lock up the FP completely (which is the risk with layouts like Example 1 above), a bit of time between requesting an action and the completion of the action doesn't seem that bad to me as long as I can inform the user that the program is processing something.

 

As for the queued example...there is a problem with simply wiring the output of the queue to the case structure selector as this would preclude the ability to enter into one case after another without enqueuing elements in the consumer loop.  For instance, say I have a process that executes State 1 on user input, then either State 2 or State 3 based on the output of State 1.  If the queue used for communication to the consumer loop is wired to the case selector, I would have to enqueue a new command in State 1 to go to the appropriate following state.  In that time the user could hit another button, causing a race condition within the queue.  I can see some ways to code around this, but none of them are particularly elegant.  Do you have a preferred method for handling this?

 

CLAD
0 Kudos
Message 7 of 12
(7,579 Views)

I don't think you fully understand how to use a queued state machine. States themselves can add elements to the queue or you could queue up all the necessary states for a given event. There are plenty of examples of how to implement this. Also, having written tons of applications I can assure you that it is generally a good idea to allow the user to stop whenever they chose. Yes, this will require you to perform cleanup actions and in some cases the stop may not be immediate. However, your application has control over this. You can also provide feedback to the user that you are processing the request but it is not complete yet.

 

By having a slow UI responsiveness (and this applies to more than just a stop button. If the application doesn't provide feedback to the user in a timely fashion what will probably occur is that the user will hit the button a few more times. Next thing you know you have multiple events queued up and you end up duplicating certain tasks.

 

In all but the most simple applications handling the UI within the processing task can be problematic. You will have slow user responsiveness, you may have to place special logic to maintain where you are in the porcessing and what functionality is avaibable, as well as locking the UI and the processing task together. This greatly restricts your ability to reuse code.

 

Your proposed solution does not eliminate multiple inputs from the user. As per my example above if the application is slow the user can press the button multiple times or change the value of a control multiple times. You will end up with multe events queued for handling in the event structure. To restrict what the user can and cannot do during execution you can always enable and disable controls as necessary.



Mark Yedinak
Certified LabVIEW Architect
LabVIEW Champion

"Does anyone know where the love of God goes when the waves turn the minutes to hours?"
Wreck of the Edmund Fitzgerald - Gordon Lightfoot
0 Kudos
Message 8 of 12
(7,563 Views)

I'm sure there is little that I fully understand.  Disabling controls was what I was thinking for preventing multiple user inputs or race conditions as that's essentially what the event structure does when "FP locking" is enabled, right?  The reason I thought that a bit inelegant is that it would require a bunch of logic at the beginning of every state deciding whether to disable the control that initiated the command or not, unless I'm missing some other way of doing it.  I suppose this could be a generic state that preceeds any command, whcih would save a bunch of code space.

 

In any case, I built the application to its current form on a JKI-like model where I have a line feed delineated "queue" from which each state can add or delete steps as needed.  States are "queued" and "dequeued" on either side of the main case structure.  The flexibility of this layout in regards to code reuse, error handling, and maintainability has been great, and I'd like to keep that while improving my UI handling archetecture.  I suppose I could rewrite the state "parsing" subVIs that currently act on the aforementioned string queue to perform the same using a standard queue.  Basically flushing the queue, analyzing the queued sequence for problems, and requeueing as needed inbetween each state of the state machine would allow me to use the same code that I currently use while providing a means to isolate the UI stuff in a separate loop.

 

I appreciate all your input while I'm learning this stuff.  Thanks!

CLAD
0 Kudos
Message 9 of 12
(7,553 Views)

To answer your last question: That is exactly why I do not like using a queue to carry the state.  I send "Commands" from the user interface loop to the processing loop (via a queue) and interpret those commands according to the current state.

 

For example suppose that the user presses a "Save Data to File" button. If the processing loop has states to Read data from sensors, one to Average and filter the data, one to Invert the data, and one to Save the data, but only data which has been through the other three states can be saved, then the Save Command must be carried forward until a Read-Average-Invert cycle is complete.  On the other hand a Stop Now command may go directly to the Halt state regardless of the Current state at the time that the command is generated.

 

Lynn

Message 10 of 12
(7,546 Views)