Automaton navigation#
This section presents navigation in a state machine, which means how to find states, transitions, forks, and how to explore the state machine structure.
Given a Swan project with the operator point declared with the following interface:
node point (u: bool;
d: bool;
r: bool;
l: bool;
unlock: bool;)
returns (x: int32 last = 0;
y: int32 last = 0;)
and with the behavior defined by the state machine:
The following code gets the automaton:
app = ScadeOne()
app.load_project("Position.sproj")
model = app.model
op = model.operator_definitions[0]
diagram = op.diagrams[0]
# automaton is the 1st object in the diagram
position_automaton = cast(StateMachineBlock, diagram.objects[0]).state_machine
Get the initial state#
The initial state of the state machine can be accessed using the StateMachine.initial_state property:
and is the Stop state in this example.
initial_states = position_automaton.initial_state
assert len(initial_states) == 1
assert str(initial_states[0].id) == "Stop"
stop_state = initial_states[0]
Get target states from a specific state#
The following code gets all direct target states reachable from the Stop state:
stop_state_targets = stop_state.get_targets()
expected = {"Up", "Down", "Left", "Right", "Center"}
target_ids = set(str(state.id) for state in stop_state_targets)
assert target_ids == expected
Explore state transitions#
Out-going transitions of the Stop state are explored, going through forks if any, and a dot graph is generated.
buffer = "digraph G {\n"
for s in position_automaton.states:
buffer += f' {s.id} [label="{s.id}[{s.lunum}]"]\n'
d = deque(stop_state.strong_transitions + stop_state.weak_transitions)
while d:
transition = d.popleft()
transition_info = ""
match transition.head:
case State(id=id_):
target = str(id_)
transition_info = "resume" if transition.is_resume else "restart"
case Fork() as fork:
target = "fork"
d.extend(fork.transitions)
The generated graph is:
digraph G {
Stop [label="Stop[#1]"]
Up [label="Up[#3]"]
Down [label="Down[#5]"]
Left [label="Left[#7]"]
Right [label="Right[#9]"]
Center [label="Center[#11]"]
Stop -> fork [label="unlock\n<strong>"]
Stop -> Center [label="c\n<strong>"]
fork -> Up [label="u\n<restart>"]
fork -> Down [label="d\n<restart>"]
fork -> Left [label="l\n<restart>"]
fork -> Right [label="r\n<restart>"]
}
This code can be visualized using any online graphviz viewer. The result is:
Get a specific state by its name#
The following code gets a specific state by its name:
down_state = position_automaton.get_state("Down")
assert down_state is not None
assert str(down_state.id) == "Down"
Accessing transition details#
The following code shows how to access transition details such as its source (tail), destination (head), and guard condition.
transition = down_state.strong_transitions[0]
assert isinstance(transition.head, State) and str(transition.head.id) == "Stop"
assert isinstance(transition.tail, State) and str(transition.tail.id) == "Down"
assert transition.is_resume is False
assert transition.guard is not None
guard = swan_to_str(transition.guard)
assert guard == "not d"
assert transition.action is None
Complete example#
# Copyright (C) 2024 - 2026 ANSYS, Inc. and/or its affiliates.
# SPDX-License-Identifier: MIT
#
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import os
from pathlib import Path
from typing import cast
from collections import deque
from ansys.scadeone.core import ScadeOne
from ansys.scadeone.core.swan import StateMachineBlock, State, Fork, swan_to_str
os.chdir(Path(__file__).parents[4] / "examples" / "models" / "Position")
# Load project and get Position automaton
app = ScadeOne()
app.load_project("Position.sproj")
model = app.model
op = model.operator_definitions[0]
diagram = op.diagrams[0]
# automaton is the 1st object in the diagram
position_automaton = cast(StateMachineBlock, diagram.objects[0]).state_machine
# Get initial Stop state
initial_states = position_automaton.initial_state
assert len(initial_states) == 1
assert str(initial_states[0].id) == "Stop"
stop_state = initial_states[0]
# Get target states from Stop state
stop_state_targets = stop_state.get_targets()
expected = {"Up", "Down", "Left", "Right", "Center"}
target_ids = set(str(state.id) for state in stop_state_targets)
assert target_ids == expected
# Explore all transitions from Stop state
buffer = "digraph G {\n"
for s in position_automaton.states:
buffer += f' {s.id} [label="{s.id}[{s.lunum}]"]\n'
d = deque(stop_state.strong_transitions + stop_state.weak_transitions)
while d:
transition = d.popleft()
transition_info = ""
match transition.head:
case State(id=id_):
target = str(id_)
transition_info = "resume" if transition.is_resume else "restart"
case Fork() as fork:
target = "fork"
d.extend(fork.transitions)
match transition.tail:
case State(id=id_, lunum=lunum):
source = str(id_)
transition_info = "strong" if transition.is_strong else "weak"
case Fork():
source = "fork"
guard = f"{swan_to_str(transition.guard)}\\n" if transition.guard else ""
edge = f' [label="{guard}<{transition_info}>"]'
buffer += f" {source} -> {target} {edge}\n"
buffer += "}\n"
print(buffer)
# Get a specific state by its name:
down_state = position_automaton.get_state("Down")
assert down_state is not None
assert str(down_state.id) == "Down"
# Transition access
transition = down_state.strong_transitions[0]
assert isinstance(transition.head, State) and str(transition.head.id) == "Stop"
assert isinstance(transition.tail, State) and str(transition.tail.id) == "Down"
assert transition.is_resume is False
assert transition.guard is not None
guard = swan_to_str(transition.guard)
assert guard == "not d"
assert transition.action is None