LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

general state machine design

Dear LabVIEWers,

 

For 1 year I am working on a "big" project and as I am learning (training on the job Smiley Wink), I am reprogramming it over and over again and again. Though I read a book about how to program LabVIEW not nearly anything is covered in a level of detail as I need it. Now I am quite fed up and so I decided to share my thoughts and ask for comments of you advanced programmers, because in my eyes in LabVIEW experience makes a HUGE difference.

My aim is to work out general good programming practices in LabVIEW, especially if the project has to be expanded on regular basis. Thus, I am trying to find general concepts that allow for good expandability and servicing (aka "oh I made up my mind, I also need an Integer now", "Now that it switches the light, can you make it that it also cooks coffee?")

 

So what my realization of the State Machine aims for is:

- easy to expand

- easy to change

- easy to apply to different tasks without using giant clusters or globals

 

This is my design proposal (till now):

img state machine.jpg

 

This state machine can perform two tasks till now, adding two numbers and wait a certain time. Both tasks need completely different inputs. The strict type def enum in the queue avoids wrong spelling as well as comfortable state queuing. Easy Adding of more tasks is obvious; just change the typedef.

The strict type defs of the corresponding tasks allow for comfortable wiring as everything is (hopefully reasonably) named, that's a big reason for me why to take a cluster here. To queue, just bundle with the typedef. To read out the variant in the consumer, the strict typedef helps again tremendously. Changing parameters for a task (e.g. adding another one) means changing the typedef.

The Variant in the queue cluster allows the adaptation to every possible task at least I could think of - just create a typedef for the new task, no need to change anything within the state machine design.

 

I am looking forward to your ideas of improvement and to share and discuss further thoughts with you.

(Things on my mind that need your input:

* How to manage a large amount of states = creating groups?

* How to keep the main vi tidy = not using 10 register cards for 10 different tasks (local variables make me cry if designed like this)

* How to save certain queues and load them again

* How to decide which states to create to ensure reusability

*...)

 

I attached the state machine in a zip file (LV10.1 (?)).

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

For a start you can find some of the things that I felt were imporatant for every LV developer to know by searching on "Ben Nugget".

 

Searching for Nugget without the "Ben" will lead you too many other interesting threads.

 

Ben

Retired Senior Automation Systems Architect with Data Science Automation LabVIEW Champion Knight of NI and Prepper LinkedIn Profile YouTube Channel
Message 2 of 8
(2,096 Views)

In general- by george I think you've got it.

 

enquing command and command specific data as variant (or flattened string) moves you from a pure QSM to what I would classify as a "step engine" A hint of one example of a step engine implementation can be found here .  The operations QSM in the model discribed is a step engine and executes quite nicely for that customer's needs. 

 

The trick to limiting the number of "specialities" or Tenors" as I call them, (the list of the enum commands) is properly encapsulating closely related functions.  I try to do this by hardware or resource and build an AE for each resource to expose the funtions (operations) I decide are desired for the application.  Ben might argue that these "Resource Modules" are not true AEs but his "passing comment" in the famous nugget actually inspred me to adopt this construct.   A useful example is attached here. 

 

If you look at the example you can see I COULD construct a "step" that called the module twice to configure and take any reading from the DMM (or combined with other modules take a wide range of actions including NO action)

 

Whatever you've read- you've found a good resource! what books do you recommend for others?

 

 

Scientists STAND on the shoulders of giants! They don't ride "piggyback" to keep their heads down.

"Should be" isn't "Is" -Jay
Message 3 of 8
(2,081 Views)

At first, this is a professional looking code I'd give a go. But sure you want others to critizise you for the sake of the mastery of the art of LV programming. So here my comments.

 

It's not a state machine. It's wrongly called a QSM (queued state machine) many times. Step engine or queued message handler are better terms. It's based on the very abstract/high-level/more-general producer-consumer or master-slave pattern.

Also this design pattern is very useful, get more tools in your tool box (Action Engine (a ueber-nugget of Ben), state machine, demon/launcher, message-bus, established OOP-patterns -> even if you don't LVOOP). Like in the real world, sometimes you need a screw-driver and at other times you need a hammer.

 

One thing I'm pretty fanatic about is error handling. Especially at early stages of a project, this should be done in a solid fashion. Hence, having thought about it for the design patterns, it's reusable and improves over the projects. You will find my own thoughts about this in the nuggets I wrote.

 

Propably, I just tell you where I continued my exploration at a stage similar to yours. I quickly got more and more parallel loops. So one concept is to launch them (demon design pattern) in an init state. It's seen frequently with LVOOP (Active Object), where the public methods send commands to a handler running 'in the background'.

The second issue is, that you would like to make the consumer loops to SubVIs, so they can't directly write to the front panel. You'll need a report back mechanism. Some possibilities are:

* Using references to the indicators -> only nice for a limited number and not for large data sets (a ueber-nugget of Ben).

* Reporting back using the OpenG message queue (but the event frame is locked until the report comes)

* Using user events to call back (see my Event nugget)

* Just using the same design to have a second loop on the top-level vi, where the SubVI-loops just send the commands to a top level loop serving the indicators

 

You might find my community nuggets on producer consumer very helpful, and the event-messageing framework as well. And here is a tag cloud for community nuggets.

 

Over all, I'm impressed by your coding style for just learning for 1 year. Keep on and join the forum discussions!

 

Felix

Message 4 of 8
(2,073 Views)

Ben,

I read your nuggets and I liked them a lot. I already started to use your AE design that was a reply on another question I had, and your Nugget about Type Defs fits in perfectly, as well as Darrens on Elements/Typedefs/Stricts. So, let my example be another useful application for your Nugget Smiley Wink

 

Jeff,

I tried to dig my way through your series; while I found some very interesting points starting at "boring" things like documentation of VIs I even never thought about (but as a PhD my time here is limited and I should also think of my successors Smiley Tongue) or using different consumer/producer queues to mimics the real process in a better fashion. Perhaps I should try to paint a flowchart for my project aswell. While I am still digesting this I fear, that the second part of your series is way over the edge for me, but I may say, from what I understood it looks really awesome and I will check the series once in a while for more input.

Concerning the book I read, it was a complete random LabVIEW starter book with not much depth on design patterns and so on, as the book wants to cover almost complete LabVIEW (or at least most common tasks) and give you a taste of it. When talking to some colleagues of mine also using LabVIEW, I often get the feeling most of them stopped after sequenced programming (luckily, not all of them). So I can only recommend the forums as a good source of improvement right now, and that's why I post here again Smiley Happy.

The idea of encapsulating "Tenors" in action engines is a good one I think, but I figured out, that I overdo one action engine all too often, and in the end there are not enough wires. Anyhow, the AE is my favorite way to move data between VIs (honestly, since I found this I hardly use anything else).

E.g. if you wanted to use a motor, you could move a step (requires step length), move absolute (requires position) and that with a certain speed, variable speeds and so on. So, do I get you right if I say: You would call "movement" Tenor, with a variant containing lets say the sub-Tenor "move step" and a variant containing the relevant information? This would lead to a clear distinguished and tidy consumer queue. The action engine does not seem too useful in this special case, but rather an ordinary state machine. Another train of thought is to use a class design and use AEs to broadcast the classes to possible sub-VIs... Probably, everything works but I wonder what is most readable, serviceable and expandable - for me that's still hard to say.

 

Felix,

Thanks for your list of potential fields of improvement, I could not agree more - the more tools I have the better the chance I choose the right one. I tried to check the list for myself and realized what you all mentioned, the caption of my thread is wrong and subject to change. I think I prefer the term step engine for the shown design.

* Action Engine/FGV - check! loving it.

* state machine - revisited the term and check

* demon/launcher - not entirely sure; is this like launching an independent VI and sending sth. to process to it once in a while? I think about using sth. like this for "RT"-Graphs (one of the seldom events (emergency stops) where I find a notifier useful) or displaying steps done during data acquisition for different Tenors. This would be the reverse solution to put consumers in sub-VIs and worry about how to send results back to the display.

* message bus - never heard of it... I guess its similar to your messageing framework?

* LVOOP coming from other programming languages, I like to program this way, as it makes code very readable. Thus I am thinking of ways how to use this in my code. I tried to read the big 4 adaption but as I do not know what they wrote I must say, I did not understand everything. Often it feels like its not worth the time creating objects; especially because I am sure I cannot use their full power in LV (this is my fault, though) and a Typedef is not much different. While writing this: How about not using variant with Typedefs but passing references to classes instead? Sounds like fun.

I shamelessly stole your error handling for my step engine to end smoothly. I did not understand why you change the error 1 to 1122, as 1122 was the error created anyhow, so I skipped that. Talking about errors; I found error handling quite useless and soon I realized, though I wire errors, I actually never really look at the errors to handle them but just wire them through. Furthermore I basically never create custom errors just for that reason. I rather abuse them as warnings to the operator. As errors might overwrite each other, might it be a good idea to log them in an array (at least for debugging)?

 

I also found another Nugget on Type Defs but this only applies to QSM as it does not allow for different "Tenors" (referring to Jeffs naming) and I dislike that front panel data are taken in the consumer loop (bad example?). I think a LV class would work better in this case, as no broadcasting of the class to other VIs is even needed.

 

Thank you all for your constructive, in depth answers so far and the insights into advanced project planning. I am very grateful for the feedback supporting and corrective aswell. For this week I will have to leave the step machine like shown below. Next week I hope to find time to improve more.

 

step engine rev 2

Message 5 of 8
(2,023 Views)

Glad to have helped-

 

In response:  you stated ...You would call "movement" Tenor, with a variant containing lets say the sub-Tenor "move step" and a variant containing the relevant information?....

Not quite.... More correctly for that example I would create a step that ran in Tenor "Move".  The tenor would call "Resource module" "Stage" with parameters passed in as flattened string (or variant)  These parameters would likely enclude both an enum "Stage Method"<Null, Init, Move Step, Move abolute, Read Pos, Home, Close> and a I32 (Step/Position) or an Array I32 if the stage has multiple axises. But I think you got the major points.  Yet.... the example in my series is a "Test Step engine" suited to running a sequence of predefined steps.  Though some branching and looping is facilitated by the engine it still is a test platform and not easilly adaptable to a "Plant" application where you tend to make more decisions and have both feedback and control loops 

 

The action engine does not seem too useful in this special case, but rather an ordinary state machine. Another train of thought is to use a class design and use AEs to broadcast the classes to possible sub-VIs...YES.   Smiley Wink I coded the series (and the app it was derived from) to be approachable by a wide variety of LabVIEW programmenrs- introducing software engineering concepts but avoiding LVOOP intentionally- Guess where my code is going to evolve towards. Smiley Very HappySmiley Very Happy 

 

When talking to some colleagues of mine also using LabVIEW, I often get the feeling most of them stopped after sequenced programming (luckily, not all of them). So I can only recommend the forums as a good source of improvement right now, and that's why I post here again :smileyhappy:.

 

One of the greatest strengths of LabVIEW is it is very easy to use- A caveman can write a VI and make it work-  "Software" can be written in LabVIEW too.  the difference? Understanding Programming.  there are a lot of working VI's that can hardly be called software since the developers did develop but did not know software engineering concetps.  ( "Bash to fit,  Paint to cover" as poor mechanical engineering is an apt analogy)

 

I fear, that the second part of your series is way over the edge for me, but I may say, from what I understood it looks really awesome and I will check the series once in a while for more input.  Don't sweat it! it really is a generalized half-step linking an AE towards data abstraction by creating a "Pseudo class" once you understand the AE, LVOOP becomes very intrigueing since, a Class adds instances and inhertance on top of what a resource module does.  Yet, it is fair to say you have to think about unloading the GUI's scope.  The app has things to do and users sometimes get in the way.  Clear and coherent scope for your vi's leads to very readable and maintainable code.  

 

You Briefly touch on error handeling- Logging to a file is a fairly good idea--- your operators are going to just call the manager and say "x application doesn't work - it gave me an error."  Now what are you going to do?  try to duplicate the exact sereies of events the user took or go look at the error log?  Here's a very basic logger without handeling

Scientists STAND on the shoulders of giants! They don't ride "piggyback" to keep their heads down.

"Should be" isn't "Is" -Jay
Message 6 of 8
(1,991 Views)

* state machine - revisited the term and check

* demon/launcher - not entirely sure; is this like launching an independent VI and sending sth. to process to it once in a while? I think about using sth. like this for "RT"-Graphs (one of the seldom events (emergency stops) where I find a notifier useful) or displaying steps done during data acquisition for different Tenors. This would be the reverse solution to put consumers in sub-VIs and worry about how to send results back to the display.

This document introduces the standard state machine as well as the deamon:

http://zone.ni.com/devzone/cda/tut/p/id/5237

The demon can act as a background server process to communicate independetly of the main process with some external code (database app, instruments). Similar to AE's with wrappers, it allows you to create an API that encapsulates (hides from the API user) the 'worker' code. In contrast to Extending the powers of an AE, it allows code to run continously.

* message bus - never heard of it... I guess its similar to your messageing framework?

Yes, similar. However, there are many ways to implement it, e.g. a combination of notifier and queues.

* LVOOP coming from other programming languages, I like to program this way, as it makes code very readable. Thus I am thinking of ways how to use this in my code. I tried to read the big 4 adaption but as I do not know what they wrote I must say, I did not understand everything. Often it feels like its not worth the time creating objects; especially because I am sure I cannot use their full power in LV (this is my fault, though) and a Typedef is not much different. While writing this: How about not using variant with Typedefs but passing references to classes instead? Sounds like fun.

When doing LVOOP, be aware that there are by-val and by-ref classes/objects. Most other languages are only by-ref. The more native way in LVOOP is to use a by-val class, that is a type def'ed cluster + encapsulation (such as done by an AE) + inheritance (and dynamic dispatching). But you can really do well without LVOOP if your project is not too big.

I shamelessly stole your error handling for my step engine to end smoothly. I did not understand why you change the error 1 to 1122, as 1122 was the error created anyhow, so I skipped that. Talking about errors; I found error handling quite useless and soon I realized, though I wire errors, I actually never really look at the errors to handle them but just wire them through. Furthermore I basically never create custom errors just for that reason. I rather abuse them as warnings to the operator. As errors might overwrite each other, might it be a good idea to log them in an array (at least for debugging)?

You're welcome to reuse the code.

The reason of the 1->1122 change is that there are many other nodes that throw error 1. In this situation, they shall be reported and not cleared.

Custom errors: I use <append> and <err> tag (wrapped them up in reuse SubVIs) to report my own error messages. Very useful when interfacing non-NI hardware (or own 'virtual instruments) to get error codes these throw displayed to the user.

Error overwriting each other: This is where I just put in a lot of effort in designing the system. If an error occures at one node, it will generate an error at another node because the input is invalid (this is most likely an error 1, see above). I learned to make sure the error wire from the nodes that produce data are given priority to the error wires from the nodes that consume the data, so I really catch the first error. Most of the time it's as simple as to have the error wire following the flow of the other data.

 

Felix

Thanks for your list of potential fields of improvement, I could not agree more - the more tools I have the better the chance I choose the right one. I tried to check the list for myself and realized what you all mentioned, the caption of my thread is wrong and subject to change. I think I prefer the term step engine for the shown design.

* Action Engine/FGV - check! loving it.

* state machine - revisited the term and check

* demon/launcher - not entirely sure; is this like launching an independent VI and sending sth. to process to it once in a while? I think about using sth. like this for "RT"-Graphs (one of the seldom events (emergency stops) where I find a notifier useful) or displaying steps done during data acquisition for different Tenors. This would be the reverse solution to put consumers in sub-VIs and worry about how to send results back to the display.

* message bus - never heard of it... I guess its similar to your messageing framework?

* LVOOP coming from other programming languages, I like to program this way, as it makes code very readable. Thus I am thinking of ways how to use this in my code. I tried to read the big 4 adaption but as I do not know what they wrote I must say, I did not understand everything. Often it feels like its not worth the time creating objects; especially because I am sure I cannot use their full power in LV (this is my fault, though) and a Typedef is not much different. While writing this: How about not using variant with Typedefs but passing references to classes instead? Sounds like fun.

I shamelessly stole your error handling for my step engine to end smoothly. I did not understand why you change the error 1 to 1122, as 1122 was the error created anyhow, so I skipped that. Talking about errors; I found error handling quite useless and soon I realized, though I wire errors, I actually never really look at the errors to handle them but just wire them through. Furthermore I basically never create custom errors just for that reason. I rather abuse them as warnings to the operator. As errors might overwrite each other, might it be a good idea to log them in an array (at least for debugging)?

Message 7 of 8
(1,957 Views)

Hello again, thanks a lot for the helpful links and tips. I read a lot about software design patterns, and the neat tricks that make a program more readable. I especially liked the images describing the overall program flow of the setup. Thus, I decided to draw an own one, I would like to hear what you think about it.

LabVIEW Flowchart v1.jpg

My Problem is quite common, as my apparatus performs various measurements and thus has some different predefined experiments, with adjustable parameters. These Experiments act as event producers. I am still quite unsure how to implement the Experiment planner (like moving experiments with drag&drop in a list, doubleclicking to open the element for adjustments... but it should be doable I guess).

I thought it would be nice to use a daemon structure to process the experiment queue, as this tends to outgrow my screen, especially when applying different Tenors...

The display Windows will use mainly notifiers, because I do not care too much if the user really sees every datapoint during acquisition; I rather care that the experiment is not delayed and everything is saved properly. Maybe I could exchange the display to various displays launched by the experiment with customized graphs and so on. As they only act as a datasink it is irrelevant if they are open or closed.

The expert window should not be used by everyone, as it breaks the queue concept - anyhow such a window helps me a lot for quick tests and trial and error operations.

Overall, I feel that this design should be easily expandable as well as easy to maintain. What do you think? What would you do differently?

 

I am still not sure if I want to use LVOOP; though I have 6 motors that makes it reasonable to use classes. I have the feeling that good clustering also does the job and the OOP overhead seems in LabVIEW feels just too big and has the possibility of numerous errors that are difficult to debug as a beginner (by value, by reference, inheritance errors, ...). Furthermore, the adaptation of "the big 4" is not easy to read and understand, especially if you did not study informatics (like me) and hence did not read the original work.

I think I saw a picture in one of Bens projects showing the dependencies through his project and his typedefs were at the very bottom - it looked just like a class tree. Anyhow, I know that OOP encapsulation makes everything much more readable and helps keeping the project tidy.

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