Skip to content
Merged
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ def ON_START(*args, **kwargs):
def HELLO(*args, **kwargs):
print("Hello!")

FSM.OPERATION.start_fsm() # prints "FSM start"
FSM.OPERATION.run_fsm("is_anyone_there") # prints "Hello!"
FSM.OPERATION.start() # prints "FSM start"
FSM.OPERATION.run("is_anyone_there") # prints "Hello!"
```

To learn more please check [the examples at the bottom of the page](#additional-links).
Expand Down
56 changes: 30 additions & 26 deletions automatabpp/automatabpp.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
__all__ = ["BEHAVIOUR", "EXECUTION", "OPERATION", "INTERFACE", "COMPARISONS"]

import logging
automata_bpp_logger = logging.getLogger(__name__)
from functools import wraps

LOGGER = logging.getLogger(__name__)

from .commandqueue.commandqueue import CommandQueue
from .comparisons.comparisons import COMPARISONS
from .constants import START_COMMAND, STOP_COMMAND, GRAPHS_DIRECTORY
from .machines.machines import Machines
from .xml.xmlread import read_graphml

__all__ = ["BEHAVIOUR", "EXECUTION", "OPERATION", "INTERFACE", "COMPARISONS", "LOGGER"]

from . machines.machines import Machines
from . xml.xmlread import read_graphml
from . commandqueue.commandqueue import CommandQueue
from . constants import GRAPHS_DIRECTORY, START_COMMAND_NAME, STOP_COMMAND_NAME
from . comparisons.comparisons import COMPARISONS

from functools import wraps


class BEHAVIOUR:
Expand All @@ -24,42 +26,42 @@ def load_behaviour_from_graph(graph_file_path: str, machine_name: str):
if graph_file_path[-8:] == ".graphml":
read_graphml("{}/{}".format(GRAPHS_DIRECTORY, graph_file_path), machine_name)
else:
automata_bpp_logger.warning("Unknown format for reading the graph file: {}".format(graph_file_path))
LOGGER.warning("Unknown format for reading the graph file: {}".format(graph_file_path))


class EXECUTION:

@staticmethod
def state(func):
current_machine = Machines().GetCurrentDefinedMachine()
if current_machine is not None:
current_machine.SetExecuteStateFunction(func.__name__, func)
machine = Machines().this_machine()
if machine is not None:
machine.set_state_function(func.__name__, func)
return func


class OPERATION:

@staticmethod
def start_fsm():
Machines().ExecuteCommand(START_COMMAND_NAME)
def start():
Machines().execute_command(START_COMMAND)

@staticmethod
def stop_fsm():
Machines().ExecuteCommand(STOP_COMMAND_NAME)
Machines().ReturnToStart()
CommandQueue().EmptyAllCommands()
def stop():
Machines().execute_command(STOP_COMMAND)
Machines().reset_machines()
CommandQueue().clear_all()

@staticmethod
def reset_fsm():
OPERATION.stop_fsm()
OPERATION.start_fsm()
def reset():
OPERATION.stop()
OPERATION.start()

@staticmethod
def run_fsm(cmd=None):
def run(cmd=None):
if cmd is None:
Machines().ExecuteAllCommands()
Machines().execute_all()
else:
Machines().ExecuteCommand(cmd)
Machines().execute_command(cmd)


class INTERFACE:
Expand All @@ -71,7 +73,9 @@ def wrapper_out(func):
def wrapper_in(*args, **kwargs):
result = func(*args, **kwargs)
if lambda_func(result):
Machines().ExecuteCommand(command)
Machines().execute_command(command)
return result

return wrapper_in

return wrapper_out
21 changes: 10 additions & 11 deletions automatabpp/commandqueue/commandqueue.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,26 @@
from automatabpp.automatabpp import automata_bpp_logger
from automatabpp.automatabpp import LOGGER
from automatabpp.metaclasses.singleton import Singleton


class CommandQueue(object, metaclass=Singleton):
SEPARATOR = "\n"

def __init__(self):
self.__list_of_cmds_to_execute = []
self.__commands = []

def __len__(self):
return len(self.__list_of_cmds_to_execute)
return len(self.__commands)

def GetNextCommand(self):
def get_next(self):
if len(self) > 0:
return self.__list_of_cmds_to_execute.pop(0)
return self.__commands.pop(0)
return None

def PushCommandsToQueue(self, commands: str):
def push_commands(self, commands: str):
for cmd in commands.split(CommandQueue.SEPARATOR):
if len(cmd) > 0:
automata_bpp_logger.debug("++++++> {} pushed to CommandQueue <++++++".format(cmd))
self.__list_of_cmds_to_execute.append(cmd)

def EmptyAllCommands(self):
self.__list_of_cmds_to_execute.clear()
LOGGER.debug("++++++> {} pushed to CommandQueue <++++++".format(cmd))
self.__commands.append(cmd)

def clear_all(self):
self.__commands.clear()
6 changes: 3 additions & 3 deletions automatabpp/constants/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
GRAPHS_DIRECTORY = "graphs"

START_STATE_NAME = "_START_"
START_COMMAND_NAME = "_start_"
STOP_COMMAND_NAME = "_stop_"
START_STATE = "_START_"
START_COMMAND = "_start_"
STOP_COMMAND = "_stop_"
33 changes: 15 additions & 18 deletions automatabpp/machine/machine.py
Original file line number Diff line number Diff line change
@@ -1,38 +1,35 @@
from automatabpp.automatabpp import automata_bpp_logger
from automatabpp.automatabpp import LOGGER
from automatabpp.constants import START_STATE
from automatabpp.state.state import State
from automatabpp.constants import START_STATE_NAME


class Machine(object):

def __init__(self, machine_name: str):
self.machine_name = machine_name
self.states = dict()
self.curr_state = self.GetStateWithName(START_STATE_NAME)
self.curr_state = self.get_state_by_name(START_STATE)

def GetActiveState(self):
return self.curr_state

def GetStateWithName(self, state_name: str):
def get_state_by_name(self, state_name: str):
if state_name in self.states.keys():
return self.states[state_name]
self.states[state_name] = State(state_name, self.machine_name)
return self.states[state_name]

def AddTransition(self, st_before: str, command: str, st_after: str):
self.GetStateWithName(st_before).SetTransition(command, st_after)
def add_transition(self, state_before: str, command: str, state_after: str):
self.get_state_by_name(state_before).set_transition(command, state_after)

def SetExecuteStateFunction(self, state_name: str, callback: callable):
self.GetStateWithName(state_name).SetExecuteStateFunction(callback)
def set_state_function(self, state_name: str, callback: callable):
self.get_state_by_name(state_name).set_callback(callback)

def ExecuteTransition(self, command: str):
st_after_name = self.curr_state.GetNextStateWithTransition(command)
def transition(self, command: str):
st_after_name = self.curr_state.transition_to_next(command)
if st_after_name is not None:
automata_bpp_logger.debug("{}: state change {}->{}".format(self.machine_name, self.curr_state.GetName(), st_after_name))
self.curr_state = self.GetStateWithName(st_after_name)
self.curr_state.ExecuteState(command)
LOGGER.debug("{}: state change {}->{}".format(self.machine_name, self.curr_state.get_name(), st_after_name))
self.curr_state = self.get_state_by_name(st_after_name)
self.curr_state.execute_state(command)
return True
return False

def ReturnToStart(self):
self.curr_state = self.GetStateWithName(START_STATE_NAME)
def reset_to_start(self):
self.curr_state = self.get_state_by_name(START_STATE)
49 changes: 23 additions & 26 deletions automatabpp/machines/machines.py
Original file line number Diff line number Diff line change
@@ -1,42 +1,39 @@
from automatabpp.automatabpp import automata_bpp_logger
from automatabpp.metaclasses.singleton import Singleton
from automatabpp.machine.machine import Machine
from automatabpp.automatabpp import LOGGER
from automatabpp.commandqueue.commandqueue import CommandQueue
from automatabpp.machine.machine import Machine
from automatabpp.metaclasses.singleton import Singleton


class Machines(object, metaclass=Singleton):

def __init__(self):
self.__list_of_machines = []
self.curr_machine_to_define = None
self.machines = []
self.machine = None

def AddNewMachine(self, machine_name: str):
self.curr_machine_to_define = Machine(machine_name)
self.__list_of_machines.append(self.curr_machine_to_define)
return self.curr_machine_to_define
def add_new_machine(self, machine_name: str):
self.machine = Machine(machine_name)
self.machines.append(self.machine)
return self.machine

def ExecuteNextCommand(self): # Some patterns could use this
next_command = CommandQueue().GetNextCommand()
def execute_next(self): # Some patterns could use this
next_command = CommandQueue().get_next()
if next_command is not None:
self.ExecuteCommand(next_command)
self.execute_command(next_command)
return True
return False

def ExecuteAllCommands(self): # Should be used as default
def execute_all(self): # Should be used as default
while len(CommandQueue()) > 0:
self.ExecuteNextCommand()

def ExecuteCommand(self, command: str): # Not recommended if states can execute commands
automata_bpp_logger.debug("Executing command [{}]".format(command))
for machine in self.__list_of_machines:
machine.ExecuteTransition(command)
self.execute_next()

def _AddTransitionToCurrDefined(self, st_before: str, command: str, st_after: str):
self.curr_machine_to_define.AddTransition(st_before, command, st_after)
def execute_command(self, command: str): # Not recommended if states can execute commands
LOGGER.debug("Executing command [{}]".format(command))
for machine in self.machines:
machine.transition(command)

def GetCurrentDefinedMachine(self):
return self.curr_machine_to_define
def this_machine(self):
return self.machine

def ReturnToStart(self):
for machine in self.__list_of_machines:
machine.ReturnToStart()
def reset_machines(self):
for machine in self.machines:
machine.reset_to_start()
28 changes: 14 additions & 14 deletions automatabpp/state/state.py
Original file line number Diff line number Diff line change
@@ -1,38 +1,38 @@
from automatabpp.automatabpp import automata_bpp_logger
from automatabpp.automatabpp import LOGGER
from automatabpp.commandqueue.commandqueue import CommandQueue


class State(object):

def __not_defined(self, **_):
automata_bpp_logger.debug("Machine:State [{}:{}] Execution not defined".format(self.machine_name, self.state_name))
LOGGER.debug("Machine:State [{}:{}] Execution not defined".format(self.machine_name, self.name))

def __init__(self, state_name: str, machine_name: str):
self.machine_name = machine_name
self.state_name = state_name
self.name = state_name
self.callback = self.__not_defined
self.transitions = dict()
self.after_execution_commands = list()
self.post_commands = list()

def GetName(self):
return self.state_name
def get_name(self):
return self.name

def SetExecuteStateFunction(self, callback: callable):
def set_callback(self, callback: callable):
self.callback = callback

def SetTransition(self, command: str, next_state_name):
def set_transition(self, command: str, next_state_name):
self.transitions[command] = next_state_name

def AddCommandToCallAfterExecution(self, cmd: str):
def add_post_command(self, cmd: str):
if len(cmd) > 0:
self.after_execution_commands.append(cmd)
self.post_commands.append(cmd)

def ExecuteState(self, command):
def execute_state(self, command):
self.callback(command=command)
for cmd in self.after_execution_commands:
CommandQueue().PushCommandsToQueue(cmd)
for cmd in self.post_commands:
CommandQueue().push_commands(cmd)

def GetNextStateWithTransition(self, command):
def transition_to_next(self, command):
if command in self.transitions.keys():
return self.transitions[command]
return None
9 changes: 5 additions & 4 deletions automatabpp/xml/xmlread.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import xml.etree.ElementTree as ET
from automatabpp.machines.machines import Machines

from automatabpp.commandqueue.commandqueue import CommandQueue
from automatabpp.machines.machines import Machines


def read_graphml(graphml_path: str, machine_name: str):
Expand Down Expand Up @@ -40,11 +41,11 @@ def __init__(self, _edge):
edges = [GraphEdge(edge) for edge in root.findall("ns0:graph/ns0:edge", read_graphml.ns)]
nodes = [GraphNode(node) for node in root.findall("ns0:graph/ns0:node", read_graphml.ns)]
node_map = dict()
new_machine = Machines().AddNewMachine(machine_name)
new_machine = Machines().add_new_machine(machine_name)
for node in nodes:
node_map[node.id] = node.name
for transition in node.transitions_after.split(CommandQueue.SEPARATOR):
new_machine.GetStateWithName(node.name).AddCommandToCallAfterExecution(transition)
new_machine.get_state_by_name(node.name).add_post_command(transition)
for edge in edges:
for transition_name in edge.name.split():
new_machine.AddTransition(node_map[edge.source], transition_name, node_map[edge.target])
new_machine.add_transition(node_map[edge.source], transition_name, node_map[edge.target])
10 changes: 5 additions & 5 deletions docs/FSM.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@ Once we `import automatabpp` into our python script we can use the following 4 c

Operation is a class that only executes the global operations on the FSMs.
```python
OPERATION.start_fsm() # Starts the FSMs by sending the '_start_' command to all the machines
OPERATION.start() # Starts the FSMs by sending the '_start_' command to all the machines

OPERATION.stop_fsm() # Stops all the FSMs by sending the '_stop_' command to all the machines,
OPERATION.stop() # Stops all the FSMs by sending the '_stop_' command to all the machines,
# reseting them to the default '_START_' state and emptying the machine commands stack.

OPERATION.reset_fsm() # Stops and starts the FSMs
OPERATION.reset() # Stops and starts the FSMs

OPERATION.run_fsm() # Runs all the commands in the CommandQueue until the queue is empty.
OPERATION.run() # Runs all the commands in the CommandQueue until the queue is empty.

OPERATION.run_fsm(cmd) # Runs only the cmd on all machines now without running the rest of the queue.
OPERATION.run(cmd) # Runs only the cmd on all machines now without running the rest of the queue.
```

### BEHAVIOUR
Expand Down
Loading