Example Code

Sequencer Template

Code and Documents

Attachment

Overview

The sequencer design pattern is a way to organize code on the block diagram without hiding code behind subVIs.

 

Description

In LabVIEW we often find ourselves writing code that extends beyond the width of our monitors. For example, VI Server or VI Scripting calls can become very wide. Classically we have addressed this problem by creating subVIs to organize our code. A subVI's main benefits are to enable code reuse, encapsulate an idea in a single unit, and enable unit testing. But to use subVIs to simply make code fit on one screen means you pay the cost of creating subVIs (documentation, unit tests, connector pane styles, etc) without the benefits of code reuse. Further, you hide implementation details from the block diagram and create unnecessary depth to your VI hierarchy.

 

I believe that creating subVIs for code organization versus reuse isn't organization at all; it is obfuscation. So I've created a very simple pattern that makes your code very readable and abstract while maintaining the low level implementation details all in one diagram. I call this pattern the "Sequencer". 

 

The sequencer is a modified state machine where each state affects the sequence only through means of returning an error. On error, the machine stops. The sequencer is based on CLAD level syntax and is implemented with arrays, case structures, for loops, a shift register and clusters.

 

Below you see the Sequencer TemplateSequencer PatternSequencer Pattern

 

  1. The array of hard coded strings represent the abstract ideas to be sequenced. This array can act as a summary of your code. 
  2. The cluster is local data used between sequences. This should not be turned into a type def unless passed into a subVI.
  3. The for loop iterates through all the sequences in the order specified by the sequence array. As a modification, you can place a case structure around the sequence array and pass a "mode" into the sequencer to change its behavior.
  4. The inner case structure contains the implementation details of each sequence.
  5. The error handler creates a custom message indicating which sequence failed and with what specific error. Upon error, the sequencer aborts. 

Example code from the Example Code Exchange in the NI Community is licensed with the MIT license.

Comments
Member
Member
on

Do you ever find it painful to put references inside of the cluster if you need them in another state?

Active Participant Active Participant
Active Participant
on

jiggawax, 

              I'm curious why you ask that?  I would think that references would work like any other piece of data.

Sam Taggart
CLA, CPI, CTD, LabVIEW Champion
DQMH Trusted Advisor
automatedenver.com
GCentral
Member
Member
on
Are you saying that rather than have a load of VI's on your block diagram you have you have the code from each of the VI's pasted into the each case statement?
Member
Member
on

"I believe that creating subVIs for code organization versus reuse isn't organization at all; it is obfuscation."

 

 

This is quite the statement!

 

Surely refactoring for the sake of readability and maintainability (and not reuse) has value in many use-cases.

Nick
Active Participant Active Participant
Active Participant
on

@jiggawax  I don't... no more than other datatypes as @Taggart has mentioned.

 

@RaymondFarmer, you mentioned

"Are you saying that rather than have a load of VI's on your block diagram you have you have the code from each of the VI's pasted into the each case statement?"

Kind of. I'm saying that I prefer to abstract where necessary. Hiding details behind subVIs is cool so long as I get the benefits of subVIs, such as reusability. However, in some types of code I find myself writing just sequencing a simple series of steps. Sometimes those steps are subVIs and sometimes they are property nodes and invoke nodes (in the case of VI Scripting as David More points out in his article at http://www.mooregoodideas.com/technical-discussion/vi-scripting-pattern/).

 

I find that summarizing the different stages of my VI as a sequence helps me see, at a 10000 foot view, what's going on by looking at the array of constants on the left side of the screen. I can also quickly see what data each sequence is using and editing by virtue of the cluster. I'm not saying subVIs are useless (in my current project I have over 3000 subVIs). Rather I'm saying that subVIs that are not part of an API or intended for reuse seem to hide my ideas rather than make them obvious.

 

 @niNickC , I often find architecting is just as much art as is it science. But as I write more and more code, it seems that long sequences of subVIs can be abstracted to an even higher level of thought by virtue of sequences. Almost like a TestStand sequence. So, as I mention above, I'm in no way advocating that we not use subVIs. Rather using subVIs to organize screen real estate (wide VIs spanning multiple monitors and beyond) seems to cause me more pain than help because the implementation details are buried one layer down from the usage of that idea. That's at the heart of my statement. 

Active Participant
Active Participant
on
Cool stuff! Not questioning the whole purpose of this pattern, but a quick question here: is there a particular reason why you chose to store the error in the "sequencer data"? As I see it, the error is not relevant to describe your sequence (of course that's debatable ;)), so I'd handle the error on its separate, independant data flow. Also, having it in the sequencer data causes some clutter in the pattern (lots of bundle/unbundle operations).

Eric M. - Senior Software Engineer
Certified LabVIEW Architect - Certified LabVIEW Embedded Systems Developer - Certified LabWindows™/CVI Developer
Neosoft Technologies inc.

Trusted Enthusiast Trusted Enthusiast
Trusted Enthusiast
on

Maybe I misunderstand the purpose of this architecture but it seems like it would be painful when dealing with nested objects.  For instance, let's say that I get a VI reference in Case A and then in Case B I get a reference to the Front Panel.  Then in Case C I get a reference to a Control and then in Case D I get a reference to some object the control contains.  Do I now have to add references for each of the objects to the cluster so I can access in later cases? 

 

I see this a lot dealing with Word or Excel where you have to drill down the containment chain to get a single cell or object.  Then at the end you have to close all of the references. 

 

Typically I'd use a SubVI to get the object I wanted to inspect.  However, it seems the purpose here is to get rid of SubVIs and shorten the width of your top level VI???? 

 

Am I missing something?

jigg
CTA, CLA
testeract.com
~Will work for kudos and/or BBQ~
Active Participant Active Participant
Active Participant
on

@ Eric.M 

That's a good catch!  There's really no need to have the error cluster in the locals cluster. I think I originally put it in there just to demonstrate how you might add \ use some local data. I didn't want to ship a broken VI because of an empty cluster. But I think I agree with you. The error can simply be passed out of the for loop using the "last value" tunnel. I'll probably update the pattern and replace the error cluster with something like a string titled "<Replace me>". Thanks!

 

@

  1. Not hiding implementation details behind subVIs unnecessarily (subVIs wouldn't have been reused)

The goal is not to avoid the usage of subVIs but to avoid the unnecessary creation of subVIs for a given type of application (a sequencing application \ algorithm) while making the code easier to ready.

As for closing references, this pattern does not change how you would close references. Hope that helps!