Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix finitestatemachne #1190

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 46 additions & 10 deletions asyncua/common/statemachine.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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
Expand Down Expand Up @@ -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())
Expand All @@ -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":
Expand All @@ -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":
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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:
Expand Down
72 changes: 11 additions & 61 deletions examples/finitestatemachine-example.py
Original file line number Diff line number Diff line change
Expand Up @@ -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=<StateNode>
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=<TransitionNode>
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())