LabVIEW Development Best Practices Discussions

cancel
Showing results for 
Search instead for 
Did you mean: 
Reply

Converting an Activity Diagram to an OOP Pattern

Solved!
Go to solution

Hello all,

I thought I might throw this out there since there are people here more knowledgeable in using OOP patterns.  I have a system that currently runs through a cycle for an autocalibration in the midst of operation.  Currently, the entire system is built off of a queued state machine and after data acquisition, the system checks to see what the current mode is, i.e. has the user specified that the system should be running through a series of calibration steps?  If the user has specified that the system should be calibrating, then the system should run through a series of steps based on time.  This means that, while in calibration mode, after data acquisition is complete the code will roll around and check the current time and then adjust certain parameters such as temperature and flows based on the current time and the previous state.  Currently, I accomplish this through a single functional global variable (attached) which will maintain a record of the current state.  It's messy and difficult to understand given that there are two different cycles with different patterns in the calibration procedure (one running within the other).  Now, the end user wants to change this and have two different calibration modes.  Below is an activity diagram for one of the modes.Ozone Activity.jpg

You can see that there are two different cycles running in this (labeled Big and Small) with one running within the other.  For the sake of maintainability (you can see a lot of case structures buried in this thing), I was considering using OOP to run through the cycles.  Would this be inappropriate (based on the garbled description of use above)?  What pattern might be most useful (I was thinking a State Pattern)?

Any thoughts would be much appreciated.

Cheers, Matt

0 Kudos
Message 1 of 19
(10,820 Views)

My advice: Ditch the supposition that you should use any formal OOP Design Patterns, and just figure out the best design for your refactoring project's goals. When I first read a couple of books on design patterns, I wasted a lot of my employer's money trying to shoehorn them into every project I was assigned. Over time, I've come to realize that they're called "patterns" because they're general representations of a way of solving coding problems that "most" people eventually discover on their own. They're not canon, and each pattern comes with a bucketful of caveats for its implementation under various constraints. These days, I get the most use out of my copy of GOF by recognizing when I've written a chunk of code a certain way, then comparing it to the reference material to see if there are any performance/maintainability tweaks I can apply to that implementation. All this is ex post facto: if I try to consult the reference first, I lose the ability to follow inspiration in my design and end up trying to force the code to look like what I read.

Stepping off my soapbox, I recommend this (re)design approach: You have a composed set of sequences: the "small cycle" is a sequence of steps, and the "big cycle" is a sequence of sequences. You can model this very well by defining a Step class and a Sequence class that inherits from it. Let the Sequence class compose an array of Step objects. Since a Sequence inherits from a Step, you can put a Sequence inside another Sequence to nest them. This is a common implementation of a data tree, and it works nicely for defining composable automation sequences:

  • Big Cycle: Sequence
    • Check Filter: Step
    • Small Cycle: Sequence
      • Set O2 Flow: Step
      • Check Bypass: Step
      • Check Spk: Step
      • Set T: Step
      • Check Temperature: Sequence
        • Evaluate T: Step

...and so on.

Honestly, I don't see any instances of statefulness in your diagram. Maybe it's not giving the full picture, but I'm pretty sure everything in this automation uses input values to generate output values or transition decisions immediately, with no need to change a step's behavior based on some state variable.

Note: It looks like you described a few transition criteria as if they were steps: Filter == TRUE, Bypass == TRUE and Spk == TRUE. What happens if any of those is FALSE?

Note 2: you forgot to attach your FGV.

Message 2 of 19
(2,959 Views)

Arrrrggggh!  Just spent the past (insert long period here) trying to respond to this post and broke the thing by clicking on advanced editor thus losing everything!  Sigh....

Ok - in response to your soapbox: With my work, I have ample time and opportunity to invest in refactoring (a luxury for most).  I see trying to figure out how to apply patterns as helping me along the way with my own design and development.  Inheritance and abstraction are awesome, but being able to understand how to apply an OOP pattern takes OOP to that next level - OOD.  I have had several instances where I have mucked around for a while trying to come up with a solution amongst a myriad of them only to hit the gas and go with my "inspiration" discovering later that there was a better and more obvious solution that would have helped me maintain and potentially reuse the software (I gave a presentation to that effect at a recent CLD summit).  Here I am trying to do what you talk about - I have a package of code that I have developed (and it serves the current purpose well), but I have the opportunity to reconsider the design potentially making it better and in the process improving my own skills.  I was hoping that, based on the pattern, something might jump out at me as similar.  Now on to the topic....

You beat me to the punch in your response.  You are correct - there are no readily identifiable discreet states.  However, there is a state that needs to be maintained and this is the state that answers question - where am I?  We need to know currently where we are in the cycle to know how to proceed to the next step.  For instance, if we just completed the "Big Cycle" then we need to know if that was the last set of data we need to cycle through.  If it wasn't, we will need to do all of those steps you have outlined above (which can be done in parallel, but that is beside the point) and then wait for the current T to equal the Tset.  Once it does we begin to step through (for certain periods of time) all of the small cycle steps and then repeat.  To be honest, your suggestion is probably good but I don't fully understand it.  How does the system know exactly where to go if you have to act on certain conditions (in this case time and temperature - T)?  Aren't these part of a state data that must be maintained?  I guess that we could maintain a list of commands that must be executed (Change Spk, Change Flow, etc.), but 1) this seems cumbersome and 2) a still can't see how this is done with out having some state data associated with the cycle.

Rethinking what I put up, I think the action states such as Spk = False, Bypass = False (forget the == which is a test for equivalency; these are actually commands)  and Set O2 Flow should be signals that are sent out to change a value.  Considering that these are actually commands that will be sent to the instrument, I think I could model the class design like this:

Model Classes.jpg

The actual implementation of the class State is meaningless to you  - but suffice it to say that we have two different implementations of those cycles (Big and Small) that you suggest be an overarching Sequence class.  I guess you could say that your Step class here is represented by Command. This is obviously different than what you suggested - here I maintain the state of the sequences using my state class (this will keep a record of the details of how we want the sequences to run as well as where we are in the cycle).  When we go to check the current state condition and find that it is time for the next, we issue a command based on 1) what kind of sequence we are and 2) where we are in the sequence (represented by the Command class). But I am still not sure about the actual implementation.

Thanks for taking the time to respond to all of this, David.  It is always nice bouncing ideas off of someone more knowledgable than myself.  I will see if I can get that VI up (can't seem to find the "Attach file" button anywere...)

Matt

0 Kudos
Message 3 of 19
(2,959 Views)

Added VI currently doing the lifting.

0 Kudos
Message 4 of 19
(2,959 Views)
This widget could not be displayed.
Solution
This widget could not be displayed.

mtat76 wrote:

Arrrrggggh!  Just spent the past (insert long period here) trying to respond to this post and broke the thing by clicking on advanced editor thus losing everything!  Sigh....

Years of providing tech support on forums.ni.com taught me to select all text and copy it to the clipboard before every clicking "submit" on a web form.

To be honest, your suggestion is probably good but I don't fully understand it.

Start with this pattern, then tweak it to meet your application's needs: https://en.wikipedia.org/wiki/Composite_pattern

How does the system know exactly where to go if you have to act on certain conditions (in this case time and temperature - T)?  Aren't these part of a state data that must be maintained?  I guess that we could maintain a list of commands that must be executed (Change Spk, Change Flow, etc.), but 1) this seems cumbersome and 2) a still can't see how this is done with out having some state data associated with the cycle.

I have to approach two disctinct topics at once to respond here, and I should take this opportunity to warn you that I was never formally trained in the language of Computer Science; I may misspeak or reassign the meaning of a term on accident. (1) When a fork or loop needs to be added to the sequence, you'll have to evaluate a condition to decide which step/subsequence to execute, whether to loop or break, etc. You can create a step to make this evaluation, or you can embed the evaluation logic in a method of the executive class (which would define the program that's running this automation sequence). Now the evaluation of conditions is a fixed behavior in your application diagram, so that input values are used to generate a decision in an explicit manner. (2) Statefulness affects behavior, not data. An object's current state is described by unique data, so sometimes that fact can be confusing...but the purpose of defining and storing state data is to evaluate which behavior an object should use when responding to a method/command/request. So if your decision behavior -- how it manipulates input values to determine its outputs -- never changes, then there's no need for statefulness in the decision step.

As for it being cumbersome to create unique objects for every action that your automation executive takes...I don't know a better approach to modularizing an automation sequence. As supporting material, I provide NI TestStand to the court, which uses this approach to define modular sequences for test automation. Every action in TestStand either is a step or a (sub)sequence.

I'd like to respond to your diagram in detail, but I have a date. It is Friday, after all.

0 Kudos
Message 5 of 19
(2,959 Views)

Here is just a quick class diagam of what I would have done, without knowing too much of your requirments.

Calcycle.png

0 Kudos
Message 6 of 19
(2,959 Views)

OK, I will reply.

Your thought to use the State Pattern is a good one.  (In fact, I highly recommend you do so.)  Your system does have multiple states, since the transition behavior very much depends on the current state.  With the State Pattern, it is straightforward to implement hierarchies of states, which if I understand the system correctly apply here.

Regardless of how you go about implementing this, I suggest you begin by creating a coherent state machine model.  I think the UML is a great tool to use for this and clearly you already have some familiarity here.  My NI Week presentation TS 8237: "State Pattern Implementation for Scalable Control Systems" walks through how to analyze a reactive system to create a state machine behavorial model (and then shows how to implement that in code, which is quite straightforward once you have the model correct).

Some hints:

Which transitions occur when a user issues a command (i.e., clicks a button)?  (These are SignalEvents.)

Which transitions occur based on checks (time has elapsed) or comparisons with previously stored values (e.g., configuration parameters or parameters from a command)?  (These are ChangeEvents.)

Note that you can use a macro command here (see Command Pattern) to step through the sequence and this would be a valid approach, but I don't think this will be necessary for your system, since I think it will be easy for each state to know where to go next quite easily.  [Aside: It is imperative to differentiate command sequences from states.  Norm's NI Week presentation "The Rebirth of the 'LabVIIEW State Machine'" got this exactly correct when pointing out the problems with the "NI state machine" and similar sequencers--even if the revised approach in that session fell short of an ideal implementation.  (The State Pattern would have provided a better answer; the solution in the presentation nevertheless makes several key steps forward.)]

If I understand your description correctly then you have states within states.  (Calibration is a substate and probably has its own substates).

You can start with your activity diagram, cleaning it up as you noted.  As you already stated, assignment and comparison are confused.  Some of the decision nodes are missing criteria.  Again, which transitions are SignalEvents and which are ChangeEvents?

The Gang of Four Design Patterns, properly used, are indeed quite powerful, once one knows how to use them.  Honestly, I would just say to our team, "We will use the State Pattern here" and we would all know the implementation architecture immediately.  The only thing we would concentrate on would be getting the behavioral analysis (state machine diagram) correct.

(Aside: I think the key to understanding the GoF Design Patterns is to understand interfaces, especially since these are not often discussed in LabVIEW contexts. That is why I address interfaces so carefully in my NI Week presentation and elsewhere.)

Message 7 of 19
(2,959 Views)
Highlighted

Sorry I let this one slide and didn't respond immediately to David's post, but here is an update: Once I understood what was being suggested with the composite pattern, I thought this wasn't too bad an idea so I have run with it.  I will show you guys how I implemented it once I get a chance.

Mikhael - Check out David's link.  I haven't seen this pattern as I don't have the GoF book (need to get it) and it hasn't been presented here (surprisingly).  It looks kinda-sorta like your implementation.

Paul - thanks for the suggestion.  I will take a closer look at the presentation (I reviewed your document here before posing this question).  That being said, I did have a tough time breaking things into concrete states much as I would with a standard state machine (such as initialize, acquire, wait, yadayadayada).  For right now, David's analogy is probably spot on with TestStand.  But, I can envision a scenario with more potential user interaction where the transitions are not merely part of a sequence (such as forcing a transition, pausing, maintaining a state, etc.).

David - a date?  Aren't you an engineer? 

0 Kudos
Message 8 of 19
(2,959 Views)

paul.dct wrote:

--even if the revised approach in that session fell short of an ideal implementation.  (The State Pattern would have provided a better answer; the solution in the presentation nevertheless makes several key steps forward.)

That's the exact same thing I thought after the presentation.  Norm and I spent some time talking about it afterwards and he's almost on board with the State Pattern. 

0 Kudos
Message 9 of 19
(2,959 Views)

OK, so I took David's idea and ran with it and here is the resulting class structure:

Composition SEQ.jpg

I apologize that it is difficult to see, but you can get a general idea of the structure from the wiki entry for which David provided a link above.  The main difference lies in the concrete classes for the leaves and sequences.  In addition, I have a SUB class which represents subsequences. 

Some points about my realization of this pattern:

  • The SEQ class represents a sequences of steps and is therefore composed of at least 1 STEP.  Instances of the STEP class are stored in a queue.  I am sure that you could do this multiple different ways, but this is the approach I took.
  • All classes have to override the abstract execution method.  Since the SUB class is just a sequence called by another sequence, the call to this method is recursive.
  • Execution of steps may occur over multiple calls - this allows me to interrupt processes that might ordinarily occur over multiple iterations (such as waiting for a timeout).
  • Messages regarding sequence execution are returned via a user event.

I realize that my interpretation is not necessarily the best.  One weakness is the use of a queue for maintaining a sequence of steps.  While I can add elements at the beginning or end of the queue, if I want to insert a new step at another location, the code would require substantial jiggering.

Anyway, thanks for your help.  This has worked nicely and in the case of calibration sequences is probrably superior to the use of a state pattern.  That being said, if the state of the system can not necessarily be determined prior to runtime (i.e. the next step queued depends on the results from the previous step), some implementation of the state pattern might be best.

For your edification, I have attached the base code to the original post (i.e. that which is not implementation dependent).  Let me know if you have any questions, comments or suggestions.  Cheers, m

Message 10 of 19
(2,959 Views)