diff --git a/asyncua/common/statemachine.py b/asyncua/common/statemachine.py index d93b9dad5..b9c60142b 100644 --- a/asyncua/common/statemachine.py +++ b/asyncua/common/statemachine.py @@ -78,7 +78,7 @@ class StateMachine(object): LastTransition: Optional "TransitionVariableType" Generates TransitionEvent's ''' - def __init__(self, server: Server=None, parent: Node=None, idx: int=None, name: str=None): + def __init__(self, server: Server=None, parent: Node=None, idx: int=None, name: str=None, typeid: ua.NodeId=ua.NodeId(2299, 0)): if not isinstance(server, Server): raise ValueError(f"server: {type(server)} is not a instance of Server class") if not isinstance(parent, Node): @@ -91,7 +91,7 @@ def __init__(self, server: Server=None, parent: Node=None, idx: int=None, name: self._server = server self._parent = parent self._state_machine_node: Node = None - self._state_machine_type = ua.NodeId(2299, 0) #StateMachineType + self._state_machine_type = typeid self._name = name self._idx = idx self._optionals = False @@ -122,7 +122,7 @@ async def install(self, optionals: bool=False): ) if self._optionals: self._last_transition_node = await self._state_machine_node.get_child(["LastTransition"]) - children = await self._last_transition_node.get_children() + children: List[Node] = await self._last_transition_node.get_children() childnames = [] for each in children: childnames.append(await each.read_browse_name()) @@ -141,9 +141,9 @@ async def init(self, statemachine: Node): initialize and get subnodes ''' self._current_state_node = await statemachine.get_child(["CurrentState"]) - current_state_props = await self._current_state_node.get_properties() + current_state_props: List[Node] = await self._current_state_node.get_properties() for prop in current_state_props: - dn = await prop.read_display_name() + dn: ua.LocalizedText = await prop.read_display_name() if dn.Text == "Id": self._current_state_id_node = await self._current_state_node.get_child(["Id"]) elif dn.Text == "Name": @@ -156,9 +156,9 @@ async def init(self, statemachine: Node): _logger.warning(f"{await statemachine.read_browse_name()} CurrentState Unknown property: {dn.Text}") if self._optionals: self._last_transition_node = await statemachine.get_child(["LastTransition"]) - last_transition_props = await self._last_transition_node.get_properties() + last_transition_props: List[Node] = await self._last_transition_node.get_properties() for prop in last_transition_props: - dn = await prop.read_display_name() + dn: ua.LocalizedText = await prop.read_display_name() if dn.Text == "Id": self._last_transition_id_node = await self._last_transition_node.get_child(["Id"]) elif dn.Text == "Name": @@ -213,7 +213,6 @@ async def _write_state(self, state: State): async def _write_transition(self, transition: Transition): ''' transition: Transition - issub: boolean (true if it is a transition between substates) ''' if not isinstance(transition, Transition): raise ValueError(f"Statemachine: {self._name} -> state: {transition} is not a instance of StateMachine.Transition class") @@ -283,14 +282,51 @@ class FiniteStateMachine(StateMachine): ''' Implementation of an FiniteStateMachineType a little more advanced than the basic one if you need to know the available states and transition from clientside + The FiniteStateMachineType is Abstract and can't be instantiated ''' - def __init__(self, server: Server=None, parent: Node=None, idx: int=None, name: str=None): + def __init__(self, server: Server=None, parent: Node=None, idx: int=None, name: str=None, typeid: ua.NodeId=ua.NodeId(2299, 0)): super().__init__(server, parent, idx, name) if name is None: self._name = "FiniteStateMachine" - self._state_machine_type = ua.NodeId(2771, 0) + self._state_machine_type = typeid self._available_states_node: Node = None self._available_transitions_node: Node = None + self._finitestatemachine_nodeid = ua.NodeId(2771, 0) + + async def install(self, optionals: bool=False): + if await self._is_subtype_of_finitestatemachine(): + await super().install(optionals) + pass + else: + raise ua.UaError(f"NodeId: {self._state_machine_type} is not a subtype of FiniteStateMachine!") + + async def _is_subtype_of_finitestatemachine(self): + result = False + type_node = self._server.get_node(self._state_machine_type) + parent = await type_node.get_parent() + if not parent: + raise ua.UaError("Node does not have a Parent!") + if parent.nodeid == self._finitestatemachine_nodeid: + result = True + else: + while not parent.nodeid == self._finitestatemachine_nodeid: + parent = await parent.get_parent() + if not parent: + result = False + break + if parent.nodeid == self._finitestatemachine_nodeid: + result = True + break + if parent.nodeid == self._server.nodes.root.nodeid: + result = False + break + return result + + async def add_state(self, state: State, state_type: ua.NodeId=ua.NodeId(2307, 0), optionals: bool=False): + raise ua.UaError("Unable to add a state to a FiniteStateMachine all states and transitions are defined in the SubType of FiniteStateMachineType") + + async def add_transition(self, transition: Transition, transition_type: ua.NodeId=ua.NodeId(2310, 0), optionals: bool=False): + raise ua.UaError("Unable to add a transition to a FiniteStateMachine all states and transitions are defined in the SubType of FiniteStateMachineType") async def set_available_states(self, states: List[ua.NodeId]): if not self._available_states_node: diff --git a/examples/finitestatemachine-example.py b/examples/finitestatemachine-example.py index 5b507faea..f01022309 100644 --- a/examples/finitestatemachine-example.py +++ b/examples/finitestatemachine-example.py @@ -12,75 +12,25 @@ async def main(): await server.init() server.set_endpoint("opc.tcp://0.0.0.0:4840/freeopcua/server/") idx = await server.register_namespace("http://examples.freeopcua.github.io") + + # creating a own FiniteStateMachine as Subtype of FiniteStateMachineType + finitestatemachinetype = server.get_node(ua.NodeId(2771, 0)) + myfinitstatemachine = await finitestatemachinetype.add_object_type(idx, "MyFiniteStateMachine") + n = await myfinitstatemachine.add_object(idx, "MyState1", ua.NodeId.from_string("i=2307")) + await n.set_modelling_rule(True) + n = await myfinitstatemachine.add_object(idx, "MyState2", ua.NodeId.from_string("i=2307")) + await n.set_modelling_rule(True) # get a instance of the FiniteStateMachine-Class def "__init__(self, server=None, parent=None, idx=None, name=None):" - mystatemachine = FiniteStateMachine(server, server.nodes.objects, idx, "FiniteStateMachine") + mystatemachine = FiniteStateMachine(server, server.nodes.objects, idx, "FiniteStateMachine", myfinitstatemachine) # call statemachine.install() to instantiate the statemachinetype (with or without optional nodes) await mystatemachine.install(optionals=True) - # the FiniteStateMachine provides helperclasses for states and transition each class is a representation of the state- and transition-variabletype - # if the state node already exist for example from xml model you can assign it here: node= - state1 = State("State-Id-1", "Idle", 1, node=None) - # adds the state (StateType) to the statemachine childs - this is mandatory for the FiniteStateMachine! - await mystatemachine.add_state(state1, state_type=ua.NodeId(2309, 0)) #this is a init state -> InitialStateType: ua.NodeId(2309, 0) - state2 = State("State-Id-2", "Loading", 2) - await mystatemachine.add_state(state2) - state3 = State("State-Id-3", "Initializing", 3) - await mystatemachine.add_state(state3) - state4 = State("State-Id-4", "Processing", 4) - await mystatemachine.add_state(state4) - state5 = State("State-Id-5", "Finished", 5) - await mystatemachine.add_state(state5) - - # sets the avalible states of the FiniteStateMachine - # this is mandatory! - await mystatemachine.set_available_states([ - state1.node.nodeid, - state2.node.nodeid, - state3.node.nodeid, - state4.node.nodeid, - state5.node.nodeid - ]) - - # setup your transition helperclass - # if the transition node already exist for example from xml model you can assign it here: node= - trans1 = Transition("Transition-Id-1", "to Idle", 1) - # adds the transition (TransitionType) to the statemachine childs - this is optional for the FiniteStateMachine - await mystatemachine.add_transition(trans1) - trans2 = Transition("Transition-Id-2", "to Loading", 2) - await mystatemachine.add_transition(trans2) - trans3 = Transition("Transition-Id-3", "to Initializing", 3) - await mystatemachine.add_transition(trans3) - trans4 = Transition("Transition-Id-4", "to Processing", 4) - await mystatemachine.add_transition(trans4) - trans5 = Transition("Transition-Id-5", "to Finished", 5) - await mystatemachine.add_transition(trans5) - - # this is optional for the FiniteStateMachine - await mystatemachine.set_available_transitions([ - trans1.node.nodeid, - trans2.node.nodeid, - trans3.node.nodeid, - trans4.node.nodeid, - trans5.node.nodeid - ]) - - # initialise the FiniteStateMachine by call change_state() with the InitialState - # if the statechange should trigger an TransitionEvent the Message can be assigned here - # if event_msg is None no event will be triggered - await mystatemachine.change_state(state1, trans1, f"{mystatemachine._name}: Idle", 300) + state1 = State() async with server: while 1: await asyncio.sleep(2) - await mystatemachine.change_state(state2, trans2, f"{mystatemachine._name}: Loading", 350) - await asyncio.sleep(2) - await mystatemachine.change_state(state3, trans3, f"{mystatemachine._name}: Initializing", 400) - await asyncio.sleep(2) - await mystatemachine.change_state(state4, trans4, f"{mystatemachine._name}: Processing", 600) - await asyncio.sleep(2) - await mystatemachine.change_state(state5, trans5, f"{mystatemachine._name}: Finished", 800) - await asyncio.sleep(2) - await mystatemachine.change_state(state1, trans1, f"{mystatemachine._name}: Idle", 500) + # FIXME asyncio.run(main())