diff --git a/Makefile b/Makefile index 9d111ec0cd7de5a588b8a09da922c53e163717d1..7989f1bb684584c22f5625e7532217f8e83dab7e 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ SHELL = /bin/bash # DOCKER_REGISTRY_USER:=tango-example PROJECT = lmcbaseclasses - +IMAGE_FOR_DIAGRAMS = nexus.engageska-portugal.pt/ska-docker/ska-python-buildenv:9.3.2.1 # # include makefile to pick up the standard Make targets, e.g., 'make build' # build, 'make push' docker push procedure, etc. The other Make targets @@ -40,10 +40,21 @@ lint: ## lint lmcbaseclasses Python code pylint --output-format=pylint2junit.JunitReporter src/ska > build/reports/linting.xml test-in-docker: build ## Build the docker image and run tests inside it. - @docker run $(IMAGE):$(VERSION) make test + @docker run --rm $(IMAGE):$(VERSION) make test lint-in-docker: build ## Build the docker image and run lint inside it. - @docker run $(IMAGE):$(VERSION) make lint + @docker run --rm $(IMAGE):$(VERSION) make lint + +generate-diagrams-in-docker: ## Build the docker image and generate state machine diagrams inside it. + @docker run --rm -v $(PWD):/diagrams $(IMAGE_FOR_DIAGRAMS) bash -c "cd /diagrams && make generate-diagrams-in-docker-internals" + +generate-diagrams-in-docker-internals: ## Generate state machine diagrams (within a container!) + test -f /.dockerenv # ensure running in docker container + apt-get update + apt-get install --yes graphviz graphviz-dev gsfonts pkg-config + python3 -m pip install pygraphviz + cd /diagrams/docs/source && python3 draw_state_machines.py + ls -lo /diagrams/docs/source/images/ help: ## show this help. @grep -hE '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' diff --git a/docs/source/State_Machine.rst b/docs/source/State_Machine.rst index d193e154250d4e4643c190cf7508faa798b96cf1..929d3191c289a8eae7083ee5835e774fa743db59 100644 --- a/docs/source/State_Machine.rst +++ b/docs/source/State_Machine.rst @@ -44,15 +44,12 @@ Diagrams of the admin mode state machine are shown below. Diagram of the admin mode state machine, as designed +.. figure:: images/AdminModeStateMachine_autogenerated.png + :alt: Diagram of the admin mode state machine, as implemented -.. - COMMENTED OUT FOR NOW - .. figure:: images/AdminModeStateMachine_autogenerated.png - :alt: Diagram of the admin mode state machine, as implemented - - Diagram of the admin mode state machine, automatically generated from - the implementation. The equivalence of this diagram to the diagram - above demonstrates that the machine has been implemented as designed. + Diagram of the admin mode state machine, automatically generated from + the implementation. The equivalence of this diagram to the diagram + above demonstrates that the machine has been implemented as designed. Operational state state machine @@ -82,7 +79,7 @@ The operational state state machine allows for: STANDBY and OFF. * transition between OFF and ON. -.. figure:: images/OperationalStateStateMachine_decoupled.png +.. figure:: images/OperationStateMachine_decoupled.png :alt: Diagram of the operational state state machine, as designed, ignoring coupling with admin mode @@ -110,23 +107,21 @@ separately. Diagrams of the operational state state machine are shown below. -.. figure:: images/OperationalStateStateMachine_coupled.png +.. figure:: images/OperationStateMachine_coupled.png :alt: Diagram of the operational state state machine, as designed, showing coupling with admin mode Diagram of the operational state (opState) state machine, as designed, showing coupling with admin mode -.. - COMMENTED OUT FOR NOW - .. figure:: images/OperationalStateStateMachine_coupled_autogenerated.png - :width: 80% - :alt: Diagram of the admin operational state state machine, as implemented +.. figure:: images/OperationStateMachine_autogenerated.png + :width: 80% + :alt: Diagram of the operational state state machine, as implemented - Diagram of the operational state state machine, automatically - generated from the implementation. The equivalence of this diagram - to the diagram above demonstrates that the machine has been - implemented as designed. + Diagram of the operational state state machine, automatically + generated from the implementation. The equivalence of this diagram + to the diagram above demonstrates that the machine has been + implemented as designed. Observation state machine diff --git a/docs/source/draw_state_machines.py b/docs/source/draw_state_machines.py new file mode 100644 index 0000000000000000000000000000000000000000..4823b887cf2cd3839ac43099e7b65224c2fc2a63 --- /dev/null +++ b/docs/source/draw_state_machines.py @@ -0,0 +1,50 @@ +""" +This module draws diagrams of the state machines. + +Usage: + ~/ska-src/lmc-base-classes$ docker run --rm -ti -v $PWD:/app continuumio/miniconda3 bash + + (base) root@293f3b699c9b:/opt/project/src/ska/base# history + $ conda install --yes pygraphviz + $ pip install transitions + $ apt-get update && apt-get install gsfonts + $ cd /app/docs/source + $ python draw_state_machines.py + +(Or see the top level Makefile) + +""" +import importlib +import sys + +from unittest import mock +from transitions.extensions import GraphMachine + +# local import, so we can run this without installing the whole package +sys.path.append("../../src/ska/base") +import state_machine + + +def patch_in_graph_machine(): + with mock.patch("transitions.Machine", GraphMachine): + mod = sys.modules["state_machine"] + importlib.reload(mod) + + +def draw(machine_class): + machine_name = machine_class.__name__ + graphing_options = { + "title": f"\n{machine_name}", + "show_conditions": True, + "show_state_attributes": True, + "show_auto_transitions": False, + } + machine = machine_class(None, **graphing_options) + machine.get_graph().draw(f"images/{machine_name}_autogenerated.png", prog='dot') + + +if __name__ == "__main__": + patch_in_graph_machine() + draw(state_machine.OperationStateMachine) + draw(state_machine.AdminModeStateMachine) + draw(state_machine.ObservationStateMachine) diff --git a/docs/source/images/AdminModeStateMachine_autogenerated.png b/docs/source/images/AdminModeStateMachine_autogenerated.png new file mode 100644 index 0000000000000000000000000000000000000000..6c761f570cdbdeb0fa41b6a157ffea9736bf19ed Binary files /dev/null and b/docs/source/images/AdminModeStateMachine_autogenerated.png differ diff --git a/docs/source/images/ObservationStateMachine_autogenerated.png b/docs/source/images/ObservationStateMachine_autogenerated.png index 8a06b18e1a03ac15f1664557b64f49c0f5a6fa65..2299fbbe566219224e8f7b7179828c03298d6115 100644 Binary files a/docs/source/images/ObservationStateMachine_autogenerated.png and b/docs/source/images/ObservationStateMachine_autogenerated.png differ diff --git a/docs/source/images/OperationStateMachine_autogenerated.png b/docs/source/images/OperationStateMachine_autogenerated.png new file mode 100644 index 0000000000000000000000000000000000000000..a9845db4166b4727af342d486079e2453676e091 Binary files /dev/null and b/docs/source/images/OperationStateMachine_autogenerated.png differ diff --git a/docs/source/images/OperationalStateStateMachine_coupled.png b/docs/source/images/OperationStateMachine_coupled.png similarity index 100% rename from docs/source/images/OperationalStateStateMachine_coupled.png rename to docs/source/images/OperationStateMachine_coupled.png diff --git a/docs/source/images/OperationalStateStateMachine_decoupled.png b/docs/source/images/OperationStateMachine_decoupled.png similarity index 100% rename from docs/source/images/OperationalStateStateMachine_decoupled.png rename to docs/source/images/OperationStateMachine_decoupled.png diff --git a/src/ska/base/state_machine.py b/src/ska/base/state_machine.py index 46ebf7741ca6063552579b4906de78a942919323..4096d8a1a11f3a250f3d9fa86c684af531edfde0 100644 --- a/src/ska/base/state_machine.py +++ b/src/ska/base/state_machine.py @@ -17,13 +17,15 @@ class OperationStateMachine(Machine): administratively disabled. """ - def __init__(self, callback=None): + def __init__(self, callback=None, **extra_kwargs): """ Initialises the state model. :param callback: A callback to be called when a transition implies a change to op state :type callback: callable + :param extra_kwargs: Additional keywords arguments to pass to super class + initialiser (useful for graphing) """ self._callback = callback @@ -203,6 +205,7 @@ class OperationStateMachine(Machine): initial="UNINITIALISED", transitions=transitions, after_state_change=self._state_changed, + **extra_kwargs, ) def _state_changed(self): @@ -219,13 +222,15 @@ class AdminModeStateMachine(Machine): The state machine governing admin modes """ - def __init__(self, callback=None): + def __init__(self, callback=None, **extra_kwargs): """ Initialises the admin mode state machine model. :param callback: A callback to be called whenever there is a transition to a new admin mode value :type callback: callable + :param extra_kwargs: Additional keywords arguments to pass to super class + initialiser (useful for graphing) """ self._callback = callback @@ -263,6 +268,7 @@ class AdminModeStateMachine(Machine): initial="MAINTENANCE", transitions=transitions, after_state_change=self._state_changed, + **extra_kwargs, ) self._state_changed() @@ -281,12 +287,14 @@ class ObservationStateMachine(Machine): ADR-8. """ - def __init__(self, callback=None): + def __init__(self, callback=None, **extra_kwargs): """ Initialises the model. :param callback: A callback to be called when the state changes :type callback: callable + :param extra_kwargs: Additional keywords arguments to pass to super class + initialiser (useful for graphing) """ self._callback = callback @@ -442,6 +450,7 @@ class ObservationStateMachine(Machine): initial="EMPTY", transitions=transitions, after_state_change=self._state_changed, + **extra_kwargs, ) self._state_changed()