Halt
While not an actual state of the UnitStateMachine, it is advised to implement the halt mechanism of a unit. This is a flag which gets propagated to every sequence or subsequence. If the flag is active, all active states should react to it as soon as possible. Halt may also be referred to as a soft stop. In contrast to the Stop state, which interrupts active processes, a soft stop should always leave a unit in a resumable state.
Acknowledging a halt request
Every Sequence already comes with the OnHalt method, which returns TRUE exactly once when a halt was requested. This is the reason why every sequence generated by the template already contains the following snippet, which just needs to be uncommented to opt into halting:
IF OnHalt() THEN
Halting := TRUE; // Uncomment to acknowledge that the sequence is halting
END_IF
Once set, Halting stays TRUE for the remainder of the run, so individual steps can check it and react by, for instance, fast-forwarding to a step that parks the unit in a safe, resumable way:
QuickstartStep.AutomaticTransportXMoveRight:
IF _step.OnEntry() THEN
_axis.TransportX.MoveAbsoluteAsync(THIS^, position:=3.1, speed:=_data.Machine.Highspeed);
END_IF
IF Halting AND_THEN _step.OnExit() THEN
_step.SetNext(QuickstartStep.AutomaticHalt);
END_IF
A sequence is not required to react to Halting at all - short sequences, that finish by themselves in a couple of cycles anyway, are often left as-is. Doing so simply delays the reaction until the sequence (or the part of it that cannot be interrupted) has finished on its own.
Milestones and resuming
By default, halting a unit that has no notion of a safe stopping point behaves just like waiting for the sequence to finish: once it is done, the unit ends up in Idle and the next start restarts the sequence from its very beginning.
Calling Milestone at a point in the step-flow where it is safe to interrupt and later continue changes this: the unit becomes resumable. Instead of transitioning to Idle, it ends up in the non-active Halted state, remembering the step at which it was interrupted. Starting the same state again (i.e. pressing the same start-button once more) does not restart the sequence, but resumes it from the last milestone - Resuming returns TRUE and OnStart sets the current step back to the milestone instead of the beginning of the sequence.
QuickstartStep.AutomaticBegin:
IF _step.OnEntry() THEN
Milestone('Conveyor is moving until box reaches the end');
_io.ConveyorOn.Enable(TRUE);
END_IF
IF Halting THEN
_step.SetNext(QuickstartStep.AutomaticHalt);
END_IF
A sequence may call Milestone as often as it likes; only the most recently reached one is kept. If a sequence never reaches a milestone before it is halted, resuming behaves exactly like a restart.
Automatic propagation
Halting is not something that has to be wired up manually for every object a sequence controls. Whenever a sequence starts another object asynchronously and passes itself (THIS^) as the startToken/cancellationToken parameter, i.e.
_actuator.VerticalLift.MoveUpAsync(THIS^);
_timer.WaitAsync(THIS^, 2.0);
_unit.Handling.AutomaticPickAsync(startToken:=THIS^, unit:=0, targetPosition:=...);
that object automatically becomes aware of a halt request that was issued to the calling sequence. This works because a Sequence is itself an ICancellationToken - as soon as the calling sequence's Halting becomes TRUE, every object that was started with THIS^ sees HaltRequested=TRUE too, without the caller having to forward anything explicitly. The started object can then react with the very same OnHalt() / Halting := TRUE / Milestone() pattern described above.
This is exactly the mechanism that propagates a halt request across unit boundaries when one Unit starts a sequence on a linked unit - see there for a complete, cross-unit example, including when to additionally halt a linked unit explicitly on top of the automatic propagation.