LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

Architecture question: queued state machine-PC...with multiple levels?

Highlighted

Hi guys,

 

first thanks for helping me so far! I've got my program to run just fine, but before investing much time in the implementation of the GUI, I have an architecture question.

 

What I have right now are two levels:

  1. multiple producer loops (one for each program routine): state machines (with a while loop of course) that controls my respective program flow and order the instruments around. These loops do NOT run concurrently. It depends on what the user wants (he/she may not want to perform all measurement tasks and may select only 3 of 5 or so. They run sequentially
  2. multiple consumer loops: message handling loops that just act, one for each instrument/motor

What is missing: the top level VI with the GUI

I planned to drive the producer loops by treating them as consumer loops from the GUI-point of view. So they receive queued messages by the GUI and in turn enqueue their own messages to the instruments.

 

But:

Then I would have a while loop inside another while loop Isn't that bad coding practice? Is there a better way to do this with my current architecture?

I made a simple example VI of my architecture and a screenshot to illustrate.

gui labview forum.PNG

0 Kudos
Message 1 of 8
(1,834 Views)

There's nothing intrinsically wrong with having a while loop inside another while loop.  Sometimes new-ish LabVIEWers get themselves in trouble b/c of not really working through the dataflow, but the issue is not simply the existence of nested loops.

 

Your usage looks (probably) fine -- an outer queued message handling loop and an inner state-machine-like sequencing loop.  As long as you maintain awareness, as I think you have, that you can't receive the next queued message until after the inner loop terminates.  

 

As shown, your lowest level is a very helpful layer of indirection to make sure the mid-level inner loop can execute to completion very quickly.  All it has to do apparently is fire off a sequence of messages to the queues that are serviced at your lowest level.  That'll keep the middle layer quick and very responsive to new messages.

 

Implied in this however is that the code at the upper levels never just sits around and waits for responses to the messages it sent.  Presumably, the lower levels will respond asynchronously at some point in the future.  Consequently, the upper levels need some additional logic and state-variable-like information to recognize when a particular sent message has been fully acted upon.

 

I've been using this kind of architecture more lately.  It seems like a lot of overhead when coding a simple system, but pays off well for larger, more complex systems.

 

 

-Kevin P

0 Kudos
Message 2 of 8
(1,759 Views)

joptimus,

 

I cannot view the VI (I'm on LV 2012), but one thing I don't like is what happens when measurement 1 is finished? I assume you want to use your 3 instruments for measurements 2 through N as well, but the instrument queues are stuck inside your Measurement 1 state.

 

Rather than using named queues, I prefer to wrap the queue functionality inside of an action engine. Each action engine takes care of 1 queue, and you do not need to wire queue in and out because it is held in a shift register. Then you can use your instrument 1 queue (and 2 and 3) in whichever measurement state you want, just make sure to initialize it at the beginning and release it at the end of the program.

0 Kudos
Message 3 of 8
(1,754 Views)

Kevin_Price,

 

You bring up good points, especially on the fast-firing intermediate loop and possible need for synchronous messaging. I like to care of synchronous messaging between two loops with a notifier. Between a Main loop and an Instrument loop, this means the Main loop sends a notifier reference to the Instrument loop, and then the Main loop waits on that notifier. When the Instrument loop has a chance, it will send a notification back to let the Main loop know that it has processed all of the previous messages. How do you like to take care of it?

0 Kudos
Message 4 of 8
(1,752 Views)

To gregoryj:

 

I'm just a few projects into this adoption of QMH architecture, so consider this as *describing* what I tend to do rather than *prescribing* what anyone else ought to do.

 

I've used a pretty strict chain-of-command approach where there's a top-level "main brain" that's in charge of overall application state as well as most high level control and data collection.  It sends messages to itself and any of the message handlers at the next lower level.  In a sense, the top level bears ultimate responsibility for managing everything at the 2nd level.   All dependencies and coordination of 2nd level loops are mediated by way of the "main brain."

 

From the 2nd level on down, all messaging falls into a linear chain of command.  A 2nd level loop that spawns off a subservient 3rd level loop will be the only line of message communication for that 3rd level loop.  2nd level loops don't communicate with one another, nor with any 3rd level loops they didn't spawn.  They only send messages up to the "main brain", down to their own subservient 3rd level loop, or to themselves.

 

This hasn't always been the most efficient way to do things, but it was recommended to first develop experience with this kind of well-defined message routing and then later make *good* decisions about when to make exceptions.

 

Now back to the actual question.  Smiley Wink

 

My default approach for these async instrument status/data messages has looked like this:  the lower level instrument loop simply sends that message up to its parent as soon as it can.  The parent is responsible for being structured to handle receiving it.  Often, it's been simply a matter of updating a "state variable" in the parent's typedef'ed and shift-registered state variable cluster that runs through all message cases.  Sometimes it triggers a series of additional actions to be taken by the parent.  But the basic idea is that the message itself serves as both the notification and the information.

 

The code at the "main brain" ends up being kinda indirect at times, moreso than just stringing together a sequence of vi calls.  That and the strict chain of command tends to produce a whole lot of distinct message cases to code up in the main brain.  There's definitely a sense of overhead from those things.  On the other hand it's very flexible to cover a wide variety of apps and it extends *very* cleanly.

 

 

-Kevin P

Message 5 of 8
(1,736 Views)

Thanks for all the replies!

 

The inner loop with the program flow for the actual measurements is quick, but in an error case, it has to wait for a user input (signaling that the error is taken care of - it is a light barrier that has to be manually reset). Maybe I could change that behaviour somehow.

 

Of course I would use the other instruments for the next measurements, I just forgot to wire them in this example.

 

What I don't really like about this concept though is, that it is not really modular. If I want to add more measurements, I always have to modify the nested state machine. What I would like much better, is to have one state machine (that selects the measurement depending on the user input from the GUI) that is decoupled from the state machine that implements the measurement itself.

 

Is there a way to have them in some sort of parallel manner?

0 Kudos
Message 6 of 8
(1,686 Views)

What you are describing (a Top Level routine that selects, at Run Time, what Acquisition/Control/Display task(s) to run sounds, to me, like what some have called a "Plug-In" Architecture.  There have been a number of presentations and discussions about this, often involving LabVIEW Object Oriented Programming (look up, for example, Elijah Kerry's Blog on Hardware Abstraction Layers (HALs).

 

Another (non-LVOOP) way to do something similar to what you describe (which I've implemented successfully) is to use (as you have) the QMH as the "Brains" (top-level control) of the Project.  When you are ready to run a Producer/Consumer pattern to handle a bit of acquisition/control, "spawn" the Producer and Consumer ("spawn" means start them running as detached Start Asychronous Call routines, using a Named Queue that they both create (and hence don't need to pass between them).

 

Bob Schor

0 Kudos
Message 7 of 8
(1,659 Views)

@Bob_Schor wrote:

What you are describing (a Top Level routine that selects, at Run Time, what Acquisition/Control/Display task(s) to run sounds, to me, like what some have called a "Plug-In" Architecture.  There have been a number of presentations and discussions about this, often involving LabVIEW Object Oriented Programming (look up, for example, Elijah Kerry's Blog on Hardware Abstraction Layers (HALs).

 

Another (non-LVOOP) way to do something similar to what you describe (which I've implemented successfully) is to use (as you have) the QMH as the "Brains" (top-level control) of the Project.  When you are ready to run a Producer/Consumer pattern to handle a bit of acquisition/control, "spawn" the Producer and Consumer ("spawn" means start them running as detached Start Asychronous Call routines, using a Named Queue that they both create (and hence don't need to pass between them).

 

Bob Schor


I thought about the asynchronous call method, but was unsure if I could still pass information between the called P/C pattern and the top level control loop. For example parameters of the measurement "in" and status "out" (like measurement is finished).

 

So between these three methods, which one would you prefer and why?

  1. Asynchronous call
  2. Nested state machines
  3. Parallel loops that are running all the time (like the acquisition and logging loops in the "continuous acquiring and logging" project example shipping with labview. i would have 11 loops in my entire project, all running parallel, but some of them idle depending on the current status.

 

0 Kudos
Message 8 of 8
(1,641 Views)