Table of Contents

Sequence

Namespace
ZCore
Extends
Inherited Properties
Inherited Methods
Implements

Interface to the Zeugwerk Framework Sequence function block which defines the basic methods that have to be implemented.

To run a sequence and start it from the beginning this can be done and should be implemented by using RunAsync. There, a cancellation token can be used to check could be started successfully. A running sequence can be stopped by calling Stop method.

There are also several methods to make life easier for application engineers on waiting an aborting the sequence if an execution of an object fails like Await (waiting for one object), Await2 (waiting for two objects) and Await3 (waiting for three objects).

Note

While implementing a SetLogger method, the platform-dependent nature of logging limits the log-ability of the implementation. This is the reason why this specific object is marked as abstract and one of the following objects should be used instead.

The typical declaration of the function body of a sequence is

FUNCTION_BLOCK <UnitName>Sequence EXTENDS ZAux.Sequence
VAR
  _step : ZCore.Step(<UnitName>Step.Begin, <UnitName>Step.End); // i.e. ZCore.Step(GoHomeStep.GoHomeBegin, GoHomeStep.GoHomeEnd);
END_VAR

Here, <UnitName>Step is an enumeration that contains all steps that the sequence may attain. The enumeration is suggested for better readability, and may be replaced with numbers - for better traceability in a log we strongly recommend to use an enumeration though. The implementation looks like

IF NOT Busy THEN
  RETURN;
END_IF

IF OnStart(_step)
THEN
  ;// init custom sequence variables here ...
END_IF

IF OnStop()
THEN
  ;// external stop was called ...
END_IF

IF OnHalt()
THEN
  ;// Halting := TRUE; // Uncomment to acknowledge that the sequence is halting
END_IF

REPEAT
  LogStep();
 
  CASE _step.Index OF
    (* ------------------------------------------------------------------ *)
    <UnitName>Step.Begin:
    (* ------------------------------------------------------------------ *)
      IF _step.OnEntry()
      THEN
        ; // ...
      END_IF
       
      Await(..., nextStep:=<UnitName>Step.End);
 
    (* ------------------------------------------------------------------ *)
    <UnitName>Step.End:
    (* ------------------------------------------------------------------ *)
      SetBusy(FALSE);
   
  ELSE
    Abort('sequence contains an unhandled step');
  END_CASE
UNTIL _step.IsNotRepeatable() OR_ELSE NOT Busy END_REPEAT

If there are some actions which have to be done when the started object has finished, Await might not be the right statement here. Of course the Framework offers the classical approach by testing Error and Busy properties in an IF and ELSIF statement.

IF NOT Busy THEN
  RETURN;
END_IF

IF OnStart(_step)
THEN
  ;// init custom sequence variables here ...
END_IF

IF OnStop()
THEN
  ;// external stop was called ...
END_IF

IF OnHalt()
THEN
  ;// Halting := TRUE; // Uncomment to acknowledge that the sequence is halting
END_IF

REPEAT
  LogStep();
 
  CASE _step.Index OF
    (* ------------------------------------------------------------------ *)
    <UnitName>Step.Begin:
    (* ------------------------------------------------------------------ *)
      IF _step.OnEntry()
      THEN
        <object>.RunAsync(THIS^); // ...
      END_IF
       
      IF <object>.Error THEN
        // if object finished with an error do something else here
        _step.SetNext(nextStep:=<UnitName>Step.End);
      ELSIF NOT <object>.Busy THEN
        // if object finished successfully do something else
        _step.SetNext(nextStep:=<UnitName>Step.End);
      END_IF
 
    (* ------------------------------------------------------------------ *)
    <UnitName>Step.End:
    (* ------------------------------------------------------------------ *)
      SetBusy(FALSE);
   
  ELSE
    Abort('sequence contains an unhandled step');
  END_CASE
UNTIL _step.IsNotRepeatable() OR_ELSE NOT Busy END_REPEAT

The enumeration is usually implemented for several sequences which are based on one unit and looks like this:

{attribute 'qualified_only'}
{attribute 'to_string'}
TYPE <UnitName>Step :
(
  BootingBegin,
  BootingInitializeEquipment,
  BootingEnd,

  AutomaticBegin,
  AutomaticDoSomething,
  AutomaticEnd
);
END_TYPE

The following code is an example, which is taken from the Quickstart Tutorial. Note that the example uses a specialized version of Sequence, which supports logging.

FUNCTION_BLOCK PickerSequenceGoHome EXTENDS ZAux.Sequence
VAR
  _step : ZCore.Step(PickerStep.GoHomeBegin, PickerStep.GoHomeEnd);
  _timer : ZAux.Timer;
END_VAR
IF NOT Busy THEN
  RETURN;
END_IF

IF OnStart(_step)
THEN
  // init custom sequence variables here ...
END_IF

IF OnStop()
THEN
  // external stop was called ...
END_IF

IF OnHalt()
THEN
  // Halting := TRUE; // Uncomment to acknowledge that the sequence is halting
END_IF

REPEAT
  LogStep();
 
  CASE _step.Index OF
    (* ---------------------------------------------- *)
    PickerStep.GoHomeBegin:
    (* ---------------------------------------------- *)
      IF _step.OnEntry()
      THEN
        _timer.WaitAsync(2.0);
      END_IF
       
      Await(_timer, nextStep:=PickerStep.GoHomeEnd);
 
    (* ---------------------------------------------- *)
    PickerStep.GoHomeEnd:
    (* ---------------------------------------------- *)
      SetBusy(FALSE);
   
  ELSE
     Abort('sequence contains an unhandled step');
  END_CASE
UNTIL _step.IsNotRepeatable() OR_ELSE NOT Busy END_REPEAT

If you want to use this sequence in an actual program it has to be instantiated and called properly. Create an empty solution with a MAIN.PRG and insert the following snippet

PROGRAM MAIN
VAR
  Step : ZCore.Step(0, 100);
  Picking : PickerSequenceGoHome;
END_VAR
------------------------------------------
CASE Step.Index OF
  0:
    IF Step.OnEntry() THEN
      Picking.RunAsync(0);
    END_IF
 
    Picking.Cyclic();
    IF Picking.Error THEN
      Step.SetNext(99);
    ELSIF NOT Picking.Busy THEN
      Step.SetNext(20);
    END_IF
   
  20: // Idle
    ;
   
  99: // Error
    ;
 
END_CASE
Note

When using this function block it is strongly recommended to overwrite StepDecoded such that the logging mechanism of the object can write strings for distinct steps of the sequence instead of mere numbers.

Note

A sequence is also an ExecutionToken to be used as a parameter for async calls (e.g. _axis.AxisName.MoveAbsolutAsync(THIS^)). A sequence as an ExecutionToken gets informed (aborted) from an object if the originally started task has changed unexpected.

Note

A sequence cannot be recovered.

FUNCTION_BLOCK ABSTRACT Sequence EXTENDS ZCore.ExecutionToken IMPLEMENTS ZCore.ISequence, ZCore.ICancellationToken, ZCore.IObject, ZCore.IError, ZCore.IHaltable, ZCore.IStartToken, ZCore.IHaltToken, ZCore.IRecoverable

Constructor

FB_init

Standard method to run the construction code of this function block it simply sets the object state of the state machine to idle because in most cases there is no booting phase necessary

METHOD FB_init (
 bInitRetains : BOOL,
 bInCopyCode : BOOL) : BOOL

Inputs

bInitRetains BOOL

if TRUE, the retain variables are initialized (warm start / cold start)

bInCopyCode BOOL

if TRUE, the instance afterwards gets moved into the copy code (online change)

Returns

BOOL

Properties

Busy

This property returns TRUE if the object is busy executing a sequence or still in its initialization phase (if applicable). It returns FALSE if the object is idle or in an error state.

PROPERTY Busy : BOOL

Property Value

BOOL

Done

If the object is in idle state (often mentioned as "the object is not busy and not on error"), this is indicated by this property returning TRUE. A rising edge on the return of this property indicates that the execution has started or an error occured (indicated by Error

PROPERTY Done : BOOL

Property Value

BOOL

Halted

The object ended its cyclic method prematurely. Use this property to test, if the object corresponded successfully correctly to a halt command (usually issued by a CancellationToken).

PROPERTY Halted : BOOL

Property Value

BOOL

Halting

The object is currently prematurely ending its cyclic method. Use this property to test, if the object corresponded correctly to a halt command (usually issued by a CancellationToken). Use the result to appropriately react. Sequences that should be halted, but do not support a dedicated halting mechanism, should either be waited for with a timeout or stopped.

Set this property to TRUE if a halt has been issued, usually like in the following code snippet

IF OnHalt()
THEN
  Halting := TRUE;
END_IF
PROPERTY Halting : BOOL

Property Value

BOOL

HaltRequested

Indicates that any holder of this token should try to halt its process as soon as possible and transition to the Idle state.

Sequences can either be halted with the token that is passed to the sequence when RunAsync is called. Lengthy sequences should implement a dedicated halting mechanism. This can be done by checking the Halting property in steps that take a long time to execute and implement a way to prematurely halt such steps.

PROPERTY HaltRequested : BOOL

Property Value

BOOL

MilestoneActive

This method returns TRUE once a sequence uses a milestone

PROPERTY MilestoneActive : BOOL

Property Value

BOOL

ResumeRequested

Indicates that any holder of this token should resume its process after a halt occured and subsequently Resume was issued.

PROPERTY ResumeRequested : BOOL

Property Value

BOOL

Resuming

This property can be used to distinguish to test wheather a sequence was (re-)started or resumed from a previous halt. If the property returns true, the sequence's first step is set the active, if it returns true the last milestone step is resumed from.

PROPERTY Resuming : BOOL

Property Value

BOOL

Methods

Await

This method is a more convenient way to check if the execution of an object failed or was successfull also in a nice object oriented way fitting to the Zeugwerk Framework. The method can be used to check if another object is done and if so, go to the next step. The method returns TRUE if the other object is not busy and has no error.

Old style checking if an object failed or was successfully executed

IF( obj1.Error )
THEN
  Abort('Object is on error');
ELSIF( NOT obj1.Busy )
THEN
  step.SetNext(MyStepEnum.NextStep1);
END_IF

By using this await method these 7 lines can be reduced to only one

Await(obj1, MyStepEnum.NextStep1);
METHOD PROTECTED Await (
 obj1 : IObject,
 nextStep : INT) : BOOL

Inputs

obj1 IObject

interface to the object that has to be checked

nextStep INT

next step if the object is not busy and not on error

Returns

BOOL

Await2

This method is a more convenient way to check if the execution of two separate running objects failed or was successfull also in a nice object oriented way fitting to the Zeugwerk Framework. The method can be used to check if other objects are done and if so, go to the next step. The method returns TRUE if all objects are not busy and have no error.

Old style checking if two objects failed or were successfully executed

IF obj1.Error
THEN
  Abort('Object1 is on error');
ELSIF obj2.Error
THEN
  Abort('Object2 is on error');
ELSIF NOT obj1.Busy AND_THEN NOT obj2.Busy
THEN
  step.SetNext(MyStepEnum.NextStep1);
END_IF

By using this Await2 method these 10 lines can be reduced to only one

Await2(obj1, obj2, MyStepEnum.NextStep1);
METHOD PROTECTED Await2 (
 obj1 : IObject,
 obj2 : IObject,
 nextStep : INT) : BOOL

Inputs

obj1 IObject

interface to the first object that has to be checked

obj2 IObject

interface to the second object that has to be checked

nextStep INT

next step if those two objects are not busy and not on error

Returns

BOOL

Await3

This method is a more convenient way to check if the execution of three separate running objects failed or was successfull also in a nice object oriented way fitting to the Zeugwerk Framework. The method can be used to check if other objects are done and if so, go to the next step. The method returns TRUE if all objects are not busy and have no error.

Old style checking if three objects failed or were successfully executed

IF( obj1.Error )
THEN
  Abort('Object1 is on error');
ELSIF( obj2.Error )
THEN
  Abort('Object2 is on error');
ELSIF( obj3.Error )
THEN
  Abort('Object3 is on error');
ELSIF( NOT obj1.Busy AND_THEN NOT obj2.Busy AND_THEN NOT obj3.Busy )
THEN
  step.SetNext(MyStepEnum.NextStep1);
END_IF

By using this Await3 method these 13 lines can be reduced to only one

Await3(obj1, obj2, obj3, MyStepEnum.NextStep1);
METHOD PROTECTED Await3 (
 obj1 : IObject,
 obj2 : IObject,
 obj3 : IObject,
 nextStep : INT) : BOOL

Inputs

obj1 IObject

interface to the first object that has to be checked

obj2 IObject

interface to the second object that has to be checked

obj3 IObject

interface to the third object that has to be checked

nextStep INT

next step if those three objects are not busy and not on error

Returns

BOOL

Milestone

This method is part of the halting and resuming mechanism of a sequence. Call this method anywhere in your sequence to store the current step as a milestone step. This step, will be automatically restarted if a resume is requested and OnStart is called in your method. If no milestone was set manually, the resuming behaves the same as a restart (the first step of the sequence is repeated)

You can use a description to the milestone for debugging reasons. The property MilestoneActive reflect if a sequence configured any milestone or not.

METHOD PROTECTED Milestone (
 description : STRING)

Inouts

description STRING

Name

Returns the name of this sequence as it has been set by SetName

METHOD Name () : ZString

Returns

ZString

OnHalt

Use this method in the sequence to check if the sequences should be halted. The method returns false once Halting=TRUE

METHOD PROTECTED OnHalt () : BOOL

Returns

BOOL

OnStart

This method signals a start of a sequence for one cycle by returning TRUE. You can test if the sequence is resumed instead of (re-)started by checking the Resuming property.

METHOD PROTECTED OnStart (
 step : IStep) : BOOL

Inputs

step IStep

step interface which is intended to control the sequence

Returns

BOOL

OnStop

This method signals a stop of a sequence for one cycle by returning TRUE.

METHOD PROTECTED OnStop () : BOOL

Returns

BOOL

RunAsync

With this method a sequence can be started. It is possible to provide a cancellationToken to check if the sequence was started properly without any errors and/or to halt the sequence prematurely by setting cancellationToken.Halting := TRUE.

METHOD RunAsync (
 cancellationToken : ICancellationToken)

Inputs

cancellationToken ICancellationToken

SetBusy

Sets the busy flag of the sequence to its intended state; If it will be set to TRUE, the following cases have to be considered:

If it will be set to FALSE, the sequence gets to ObjectState.Idle if the sequence was on ObjectState.Busy

METHOD PROTECTED SetBusy (
 busy : BOOL)

Inputs

busy BOOL

Desired state in which the sequence should change to

SetLogger

Setting a Logger with this method enables the logging functionality of a sequence function block.

Note

To change the logging behaviour it is sometimes useful to inject a LoggerDecorator here. For more information on that pattern read the following documentation (TODO: documentation of the decorator Pattern)

Leaving this Sequence instance without logger will not create any log entries

METHOD SetLogger (
 logger : ILogger)

Inputs

logger ILogger

Zeugwerk compatible Logger which has the ILogger interface implemented

SetName

To differantiate between all different sequences they should be initialized with a name. Initialization can be done with instantiation or later by calling this SetName method. A name should be not longer than about 30 characters.

METHOD SetName (
 name : ZString)

Inputs

name ZString

name of the sequence as String; ideally not longer than 30 characters

StepDecoded

This method is used if logging is activated to convert a step enumeration to a human-readable string in LogStep. This implementation only returns stepIndex as a number. By now, a call to Codesys's TO_STRING does not work correctly in libraries. Thus, it is recommended to extend from this object when implementing a PLC and to overwrite this method to utilize TO_STRING for all enumerations that are contained in a PLC. When developing a sequence for a library, it is unavoidable to implement a CASE statement instead.

Usually a step enumeration looks like this (for converting enum numbers to strings automatically, attribute strict is not possible):

{attribute 'qualified_only'}
{attribute 'to_string'}
TYPE <SomeName>Step :
(
  Begin
  ,End
);
END_TYPE

In PLCs overwrite this method with

METHOD StepDecoded : ZCore.ZString
VAR_INPUT
  stepIndex : INT;
END_VAR
VAR_INST
  _stepIndexDecoded : <SomeName>Step;
END_VAR
-------------------------------------------------
_stepIndexDecoded := stepIndex;
StepDecoded := TO_STRING(_stepIndexDecoded);

where _stepIndexDecoded is an instance of the enumeration that is used for the steps of the sequence. In libraries overwrite the StepDecoded method with

// we can not use TO_STRING, because we want to keep the step enum internal
CASE stepIndex
OF
  <SomeName>Step.<StepName>: StepDecoded := '<StepName>';
ELSE
  StepDecoded := '<Unknown step>';
END_CASE

where <SomeName>Step refers to an enumeration that contains all attainable steps. Use numbers instead if you do not want to use such an enumeration.

METHOD StepDecoded (
 stepIndex : INT) : ZString

Inputs

stepIndex INT

step index of the sequence

Returns

ZString

Stop

This method stops a running sequence immediately.

METHOD Stop ()