How-to guide
State machines
Define finite state machines in the server with enumerations and attributes, and change their states at runtime.
How-to guide
State machines
Types usedOpcFiniteStateMachineNodeOpcStateMachineStatesAttributeOpcStateMachineTransitionAttributeOpcStateMachineTransitionsAttributeOpcStateMachineInitialStateAttribute
Many information models describe the state of a machine or a process with a finite state machine (FiniteStateMachineType as defined in OPC UA Part 16). Such a state machine has a fixed set of states and the allowed transitions between them. It provides its current state through the child nodes CurrentState and LastTransition, which clients can read and subscribe to. In the server, you define such a state machine with the OpcFiniteStateMachineNode<T> class.
Define the states
You define the states with an enumeration. The value of an entry is the number of the state, and its name is the name of the state:
public enum MachineState : uint
{
Stopped = 0,
Starting = 1,
Running = 2,
Stopping = 3
}Public Enum MachineState As UInteger
Stopped = 0
Starting = 1
Running = 2
Stopping = 3
End EnumDefine the state machine class
You define the state machine itself as a subclass of the OpcFiniteStateMachineNode<T> class and describe it with attributes:
OpcStateMachineStatesdefines the enumeration of the states.OpcStateMachineTransitiondefines one allowed transition from one state to another.OpcStateMachineInitialStatedefines the state that the state machine takes when it is created in the address space. Without this attribute, it takes the first of its states.
[OpcStateMachineStates(typeof(MachineState))]
[OpcStateMachineTransition(MachineState.Stopped, MachineState.Starting)]
[OpcStateMachineTransition(MachineState.Starting, MachineState.Running)]
[OpcStateMachineTransition(MachineState.Running, MachineState.Stopping)]
[OpcStateMachineTransition(MachineState.Stopping, MachineState.Stopped)]
[OpcStateMachineInitialState(MachineState.Stopped)]
public class MachineStateMachineNode : OpcFiniteStateMachineNode<MachineState>
{
public MachineStateMachineNode(IOpcNode parent, OpcName name)
: base(parent, name)
{
}
}<OpcStateMachineStates(GetType(MachineState))>
<OpcStateMachineTransition(MachineState.Stopped, MachineState.Starting)>
<OpcStateMachineTransition(MachineState.Starting, MachineState.Running)>
<OpcStateMachineTransition(MachineState.Running, MachineState.Stopping)>
<OpcStateMachineTransition(MachineState.Stopping, MachineState.Stopped)>
<OpcStateMachineInitialState(MachineState.Stopped)>
Public Class MachineStateMachineNode
Inherits OpcFiniteStateMachineNode(Of MachineState)
Public Sub New(ByVal parent As IOpcNode, ByVal name As OpcName)
MyBase.New(parent, name)
End Sub
End ClassIf the states follow each other in the order of the enumeration, the OpcStateMachineTransitions attribute is enough instead of the individual transitions. It defines the transitions from each state to the next one and, with IsLoop, also the transition from the last state to the first one:
// Transitions from each state to the next one and from the last state to the first one.
[OpcStateMachineStates(typeof(MachineState))]
[OpcStateMachineTransitions(typeof(MachineState), IsLoop = true)]
public class MachineCycleNode : OpcFiniteStateMachineNode<MachineState>
{
public MachineCycleNode(IOpcNode parent, OpcName name)
: base(parent, name)
{
}
}'Transitions from each state to the next one and from the last state to the first one.
<OpcStateMachineStates(GetType(MachineState))>
<OpcStateMachineTransitions(GetType(MachineState), IsLoop:=True)>
Public Class MachineCycleNode
Inherits OpcFiniteStateMachineNode(Of MachineState)
Public Sub New(ByVal parent As IOpcNode, ByVal name As OpcName)
MyBase.New(parent, name)
End Sub
End ClassCreate the state machine in the address space
You create the state machine in the node manager like any other node, for example as a child node of the machine whose state it describes:
// MyNodeManager.CreateNodes(...)
var machineNode = new OpcObjectNode(new OpcName("Machine", this.DefaultNamespaceIndex));
var machineStateNode = new MachineStateMachineNode(machineNode, "State");'MyNodeManager.CreateNodes(...)
Dim machineNode = New OpcObjectNode(New OpcName("Machine", Me.DefaultNamespaceIndex))
Dim machineStateNode = New MachineStateMachineNode(machineNode, "State")Change the state
You change the state with the ChangeStateTo method. It applies the change and notifies all clients that subscribe to the state. If there is no transition from the current state to the requested state, the method throws an InvalidOperationException. The CanChangeStateTo method determines beforehand whether a change is allowed. If the state machine is already in the requested state, the call has no effect:
if (machineStateNode.CanChangeStateTo(server.SystemContext, MachineState.Starting))
machineStateNode.ChangeStateTo(server.SystemContext, MachineState.Starting);
// Throws an InvalidOperationException, because there is no transition
// from the state Starting to the state Stopped.
machineStateNode.ChangeStateTo(server.SystemContext, MachineState.Stopped);If machineStateNode.CanChangeStateTo(server.SystemContext, MachineState.Starting) Then
machineStateNode.ChangeStateTo(server.SystemContext, MachineState.Starting)
End If
'Throws an InvalidOperationException, because there is no transition
'from the state Starting to the state Stopped.
machineStateNode.ChangeStateTo(server.SystemContext, MachineState.Stopped)The CurrentState property returns the current state, and the LastTransition property returns the most recent transition. If a state machine from an information model does not support the CurrentState child node, a subclass can indicate this with the SupportsCurrentState property. State changes then have no effect.

