diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e21a0ce8166960ab82cfa312a1cb6fc0d823fa3d..6034859aa70105ce7f465e2896e2367522ee534c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,7 +1,7 @@ # GitLab CI in conjunction with GitLab Runner can use Docker Engine to test and build any application. # Docker, when used with GitLab CI, runs each job in a separate and isolated container using the predefined image that is set up in .gitlab-ci.yml. # In this case we use the latest python docker image to build and test this project. -image: nexus.engageska-portugal.pt/ska-docker/ska-python-buildenv:9.3.2 +image: nexus.engageska-portugal.pt/ska-docker/ska-python-buildenv:9.3.2.1 include: - project: 'ska-telescope/templates-repository' @@ -29,7 +29,7 @@ stages: clean shell runner: stage: .pre tags: - - docker-executor + - k8srunner script: # Gitlab CI badges creation - rm -rf build/* @@ -37,7 +37,7 @@ clean shell runner: build wheel for publication: # Executed on a tag stage: build tags: - - docker-executor + - k8srunner script: - python3 setup.py egg_info -b+$CI_COMMIT_SHORT_SHA sdist bdist_wheel rules: @@ -49,7 +49,7 @@ build wheel for publication: # Executed on a tag build wheel for development: # Executed on non-tagged commit stage: build tags: - - docker-executor + - k8srunner script: - python3 setup.py egg_info -b+dev.$CI_COMMIT_SHORT_SHA sdist bdist_wheel rules: @@ -61,7 +61,7 @@ build wheel for development: # Executed on non-tagged commit unit tests: stage: test tags: - - docker-executor + - k8srunner script: - echo $(ls -d ./dist/*.whl | grep $CI_COMMIT_SHORT_SHA) - python3 -m pip install -U $(ls -d ./dist/*.whl | grep $CI_COMMIT_SHORT_SHA) @@ -74,7 +74,7 @@ unit tests: linting: stage: linting tags: - - docker-executor + - k8srunner script: - echo $(ls -d ./dist/*.whl | grep $CI_COMMIT_SHORT_SHA) - python3 -m pip install -U $(ls -d ./dist/*.whl | grep $CI_COMMIT_SHORT_SHA) @@ -86,7 +86,7 @@ linting: publish to nexus: stage: publish tags: - - docker-executor + - k8srunner variables: TWINE_USERNAME: $TWINE_USERNAME TWINE_PASSWORD: $TWINE_PASSWORD @@ -105,7 +105,7 @@ release docker image: before_script: - docker login -u $DOCKER_REGISTRY_USERNAME -p $DOCKER_REGISTRY_PASSWORD $DOCKER_REGISTRY_HOST tags: - - docker-executor + - k8srunner script: - make build - make push @@ -118,7 +118,7 @@ release docker image: pages: stage: pages tags: - - docker-executor + - k8srunner script: - cp -R build public - mv public/htmlcov/* public diff --git a/.release b/.release index c1c4389eb647922640cfdff017dba2f11f3f4857..87b75fde02ff38edc5a39741f2fed429127edd5a 100644 --- a/.release +++ b/.release @@ -1,2 +1,2 @@ -release=0.6.6 -tag=lmcbaseclasses-0.6.6 +release=0.7.0 +tag=lmcbaseclasses-0.7.0 diff --git a/Dockerfile b/Dockerfile index 4dbff9ecae8071140a0d8803e91adfb15427b24f..aa0aa72623bb47b01a408da63681a9f5cc16f3f8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,10 @@ # Use SKA python image as base image -FROM nexus.engageska-portugal.pt/ska-docker/ska-python-buildenv:9.3.2 AS buildenv -FROM nexus.engageska-portugal.pt/ska-docker/ska-python-runtime:9.3.2 AS runtime +FROM nexus.engageska-portugal.pt/ska-docker/ska-python-buildenv:9.3.2.1 AS buildenv +FROM nexus.engageska-portugal.pt/ska-docker/ska-python-runtime:9.3.2.1 AS runtime # create ipython profile to so that itango doesn't fail if ipython hasn't run yet RUN ipython profile create -# TODO: move this dependency to ska-docker/docker/tango/ska-python-buildenv/requirements.txt -RUN python3 -m pip install --user pytest-forked - # Note: working dir is `/app` which will have a copy of our repo # The pip install will be a "user installation" so update path to access console scripts ENV PATH=/home/tango/.local/bin:$PATH 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/README.md b/README.md index 2311a8bd88e19fad7b7cd0418a1b302814e554e9..95a0c07f1265a09513214176bcaac2140e163d1b 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,13 @@ The lmc-base-classe repository contains set of eight classes as mentioned in SKA ## Version History +#### 0.7.0 +- Separate adminMode state machine from opState state machine +- Add support for STANDBY opState +- Add Standby() and Disable() commands to SKABaseDevice +- Breaking behavioural changes to adminMode and opState state machines +- Breaking change to `_straight_to_state` method signature + #### 0.6.6 - Documentation bugfix @@ -36,7 +43,7 @@ The lmc-base-classe repository contains set of eight classes as mentioned in SKA - Refactor state machine to use pytransitions library. - Minor behavioural change: Off() command is accepted in every obsState, rather than only EMPTY obsState. -- support `straight_to_state` shortcuts to simplify test setups +- support `_straight_to_state` shortcuts to simplify test setups - Refactor of state machine testing to make it more portable #### 0.6.3 diff --git a/docs/source/SKABaseDevice.rst b/docs/source/SKABaseDevice.rst index 9d41032725d5449356f3100f4035213ed5a81c07..49deb8eb9bdb6784bacef665d92e055d32bbf406 100644 --- a/docs/source/SKABaseDevice.rst +++ b/docs/source/SKABaseDevice.rst @@ -10,6 +10,11 @@ SKA BaseDevice :maxdepth: 2 .. automodule:: ska.base.base_device + +.. autoclass:: ska.base.DeviceStateModel + :members: + :undoc-members: + .. autoclass:: ska.base.SKABaseDevice :members: :undoc-members: diff --git a/docs/source/State_Machine.rst b/docs/source/State_Machine.rst index ff7d1687b8cdd628b351bff00989fe4544c8bdc7..dfb271032b77820ee02783bdee4655c38498a5c1 100644 --- a/docs/source/State_Machine.rst +++ b/docs/source/State_Machine.rst @@ -2,35 +2,125 @@ State Machine ============= -The state machine modules implements SKA's two fundamental state machines: the -base device state machine, and the observation state machine. - -Base device state machine -------------------------- -The base device state machine provides basic state needed for all devices, -covering initialisation, off and on states, and a fault state. This state -machine is implemented by all SKA Tango devices that inherit from these LMC -base classes, though some devices with standby power modes may need to -implement further states. - - -.. figure:: images/device_state_diagram.png - :width: 80% - :alt: Diagram of the device state machine, taken from SKA design - documentation, showing the state machine as designed - - Diagram of the device state machine, taken from SKA design - documentation, showing the state machine as designed - - -.. figure:: images/BaseDeviceStateMachine.png +The state machine module implements three fundamental SKA state +machines: + +* the admin mode state machine +* the operational state (opState, represented in TANGO devices by TANGO + state) state machine +* the observation state machine. + +Admin mode state machine +------------------------ +The admin mode state machine allows for transitions between the five +administrative modes: + +* NOT_FITTED: this is the lowest state of readiness, representing + devices that cannot be deployed without some external action, such as + plugging hardware in or updating network settings.) +* RESERVED: the device is fitted but redundant to other devices. It is + ready to take over should other devices fail. +* OFFLINE: the device has been declared by SKA operations not currently + to be used for operations (or whatever other function it provides) +* MAINTENANCE: the device cannot be used for science purposes but can be + operationed for engineering / maintenance purposes, such as testing, + debugging, etc +* ONLINE: the device can be used for science purposes. + +The admin mode state machine allows for + +* any transition between the modes NOT_FITTED, RESERVED and OFFLINE + (e.g. an unfitted device being fitted as a redundant or non-redundant + device, a redundant device taking over when another device fails, etc) +* any transition between the modes OFFLINE, MAINTENANCE and ONLINE (e.g. + an online device being taken offline or put into maintenance mode to + diagnose a fault, a faulty device moving between maintenance and + offline mode as it undergoes sporadic periods of diagnosis. + +Diagrams of the admin mode state machine are shown below. + +.. figure:: images/AdminModeStateMachine.png + :alt: Diagram of the admin mode state machine, as designed + + Diagram of the admin mode state machine, as designed + +.. 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. + + +Operational state state machine +------------------------------- +The operational state (opState) machine represents the operational state +of a SKA device. It is represented in TANGO devices using the TANGO +"state", so the states used are a subset of the TANGO states: INIT, +FAULT, DISABLE, STANDBY, OFF and ON. + +* INIT: the device is currently initialising +* FAULT: the device has experienced an error from which it could not + recover. +* DISABLE: the device is in its lowest state of readiness, from which + it may take some time to become fully operational. For example, if the + device manages hardware, that hardware may be switched off. +* STANDBY: the device is unready, but can be made ready quickly. For + example, if the device manages hardware, that hardware may be in a + low-power standby mode. +* OFF: the device is fully operational but is not currently in use +* ON: the device is in use + +The operational state state machine allows for: + +* transition from INIT or FAULT into any of the three "readiness states" + DISABLE, STANDBY and OFF. +* all transitions between these three "readiness states" DISABLE, + STANDBY and OFF. +* transition between OFF and ON. + +.. figure:: images/OperationStateMachine_decoupled.png + :alt: Diagram of the operational state state machine, as designed, + ignoring coupling with admin mode + + Diagram of the operational state (opState) state machine, as + designed, ignoring coupling with admin mode + +Unfortunately, operational state is inextricably coupled with admin +mode: there are admin modes that imply disablement, and operational +states such as ON should not be possible in such admin modes. + +To facilitate this, the entire operational state state machine is +accessible only when the admin mode is ONLINE or MAINTENANCE. When in +any other admin mode, the only permitted operational states are INIT, +FAULT and DISABLE. This constraint is implemented into the operational +state state machine by + +* three extra states: INIT_ADMIN, FAULT_ADMIN and DISABLED_ADMIN +* two extra transition triggers: "admin_on" and "admin_off", which allow + for transition between INIT and INIT_ADMIN; FAULT and FAULT_ADMIN; and + DISABLE and DISABLE_ADMIN. + +This implementation minimises the coupling between admin mode and +operational state, allowing the two machines to be conceptualised almost +separately. + +Diagrams of the operational state state machine are shown below. + +.. 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 + +.. figure:: images/OperationStateMachine_autogenerated.png :width: 80% - :alt: Diagram of the device state machine, automatically generated - from the state machine as specified in code. + :alt: Diagram of the operational state state machine, as implemented - Diagram of the device state machine, automatically generated from the - state machine as specified in code. The equivalence of this diagram to - the diagram previous demonstrates that the machine has been + 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. @@ -39,31 +129,37 @@ Observation state machine The observation state machine is implemented by devices that manage observations (currently only subarray devices). -.. figure:: images/ADR-8.png +.. figure:: images/ObservationStateMachine_adr8.png :width: 80% - :alt: Diagram of the observation state machine, as decided and published in ADR-8. + :alt: Diagram of the observation state machine, as decided and + published in ADR-8. - Diagram of the observation state machine, as decided and published in ADR-8. + Diagram of the observation state machine, as decided and published in + ADR-8. -.. figure:: images/ObservationStateMachine.png +.. figure:: images/ObservationStateMachine_autogenerated.png :width: 80% - :alt: Diagram of the observation state machine, automatically generated from - the state machine as specified in code. + :alt: Diagram of the observation state machine, automatically + generated from the implementation Diagram of the observation state machine, automatically generated from - the state machine as specified in code. The equivalance of this - diagram to the diagram previous demonstrates that the machine has been - implemented in conformance with ADR-8. + the implementation. The equivalance of this diagram to the diagram + previous demonstrates that the machine has been implemented in + conformance with ADR-8. API --- -.. toctree:: - :maxdepth: 2 - - .. automodule:: ska.base.state_machine :members: :undoc-members: +.. autoclass:: OperationStateMachine + :members: + +.. autoclass:: AdminModeStateMachine + :members: + +.. autoclass:: ObservationStateMachine + :members: 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.png b/docs/source/images/AdminModeStateMachine.png new file mode 100644 index 0000000000000000000000000000000000000000..6351a89cf21b929414bfdaa2ca37028d77a32591 Binary files /dev/null and b/docs/source/images/AdminModeStateMachine.png differ 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/BaseDeviceStateMachine.png b/docs/source/images/BaseDeviceStateMachine.png deleted file mode 100644 index 805ed112bc4811383278bea4d00c88768580c7c1..0000000000000000000000000000000000000000 Binary files a/docs/source/images/BaseDeviceStateMachine.png and /dev/null differ diff --git a/docs/source/images/ObservationStateMachine.png b/docs/source/images/ObservationStateMachine.png deleted file mode 100644 index 8a06b18e1a03ac15f1664557b64f49c0f5a6fa65..0000000000000000000000000000000000000000 Binary files a/docs/source/images/ObservationStateMachine.png and /dev/null differ diff --git a/docs/source/images/ADR-8.png b/docs/source/images/ObservationStateMachine_adr8.png similarity index 100% rename from docs/source/images/ADR-8.png rename to docs/source/images/ObservationStateMachine_adr8.png diff --git a/docs/source/images/ObservationStateMachine_autogenerated.png b/docs/source/images/ObservationStateMachine_autogenerated.png new file mode 100644 index 0000000000000000000000000000000000000000..2299fbbe566219224e8f7b7179828c03298d6115 Binary files /dev/null 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/OperationStateMachine_coupled.png b/docs/source/images/OperationStateMachine_coupled.png new file mode 100644 index 0000000000000000000000000000000000000000..c2e4e248213dee574cf47f484873be631dc77734 Binary files /dev/null and b/docs/source/images/OperationStateMachine_coupled.png differ diff --git a/docs/source/images/OperationStateMachine_decoupled.png b/docs/source/images/OperationStateMachine_decoupled.png new file mode 100644 index 0000000000000000000000000000000000000000..057aef047390af60da94e7d574ad6c876b8b36cd Binary files /dev/null and b/docs/source/images/OperationStateMachine_decoupled.png differ diff --git a/docs/source/images/device_state_diagram.png b/docs/source/images/device_state_diagram.png deleted file mode 100644 index 839caca46bad985fb1d6b59e5e6728e2d1ca5a35..0000000000000000000000000000000000000000 Binary files a/docs/source/images/device_state_diagram.png and /dev/null differ diff --git a/src/ska/base/__init__.py b/src/ska/base/__init__.py index 049a6be104bcccb29eab16282adf8888bfa8f600..5a718b4d8e64968cd6bf3e1034d877529f23a44a 100644 --- a/src/ska/base/__init__.py +++ b/src/ska/base/__init__.py @@ -3,19 +3,22 @@ __all__ = ( "control_model", "state_machine", "SKAAlarmHandler", - "SKABaseDevice", "SKABaseDeviceStateModel", + "SKABaseDevice", + "DeviceStateModel", "SKACapability", "SKALogger", "SKAMaster", "SKAObsDevice", - "SKASubarray", "SKASubarrayStateModel", "SKASubarrayResourceManager", + "SKASubarray", + "SKASubarrayStateModel", + "SKASubarrayResourceManager", "SKATelState", ) # Note: order of imports is important - start with lowest in the hierarchy # SKABaseDevice, and then classes that inherit from it -from .base_device import SKABaseDevice, SKABaseDeviceStateModel +from .base_device import SKABaseDevice, DeviceStateModel from .alarm_handler_device import SKAAlarmHandler from .logger_device import SKALogger from .master_device import SKAMaster @@ -25,5 +28,7 @@ from .tel_state_device import SKATelState from .obs_device import SKAObsDevice from .capability_device import SKACapability from .subarray_device import ( - SKASubarray, SKASubarrayStateModel, SKASubarrayResourceManager + SKASubarray, + SKASubarrayStateModel, + SKASubarrayResourceManager, ) diff --git a/src/ska/base/base_device.py b/src/ska/base/base_device.py index 656922d2a75f48200be9f8b34e9a9b10f5af1881..2d8b824bad2e3141cd279271bcf8a9a8262668d9 100644 --- a/src/ska/base/base_device.py +++ b/src/ska/base/base_device.py @@ -38,12 +38,10 @@ from ska.base.control_model import ( LoggingLevel ) from ska.base.faults import StateModelError -from ska.base.state_machine import BaseDeviceStateMachine +from ska.base.state_machine import OperationStateMachine, AdminModeStateMachine from ska.base.utils import get_groups_from_json, for_testing_only -from ska.base.faults import (GroupDefinitionsError, - LoggingTargetError, - LoggingLevelError) +from ska.base.faults import GroupDefinitionsError, LoggingTargetError, LoggingLevelError LOG_FILE_SIZE = 1024 * 1024 # Log file size 1MB. @@ -59,6 +57,7 @@ class _Log4TangoLoggingLevel(enum.IntEnum): https://github.com/tango-controls/cppTango/blob/ 4feffd7c8e24b51c9597a40b9ef9982dd6e99cdf/log4tango/include/log4tango/Level.hh#L86-L93 """ + OFF = 100 FATAL = 200 ERROR = 300 @@ -136,7 +135,7 @@ class LoggingUtils: def sanitise_logging_targets(targets, device_name): """Validate and return logging targets '<type>::<name>' strings. - :param target: + :param targets: List of candidate logging target strings, like '<type>[::<name>]' Empty and whitespace-only strings are ignored. Can also be None. @@ -146,7 +145,7 @@ class LoggingUtils: :return: list of '<type>::<name>' strings, with default name, if applicable - :raises: LoggingTargetError for invalid target string that cannot be corrected + :raises LoggingTargetError: for invalid target string that cannot be corrected """ default_target_names = { "console": "cout", @@ -205,7 +204,7 @@ class LoggingUtils: - address is tuple of (hostname, port), with hostname a string, and port an integer. - socktype is socket.SOCK_DGRAM for UDP, or socket.SOCK_STREAM for TCP. - :raises: LoggingTargetError for invalid url string + :raises LoggingTargetError: for invalid url string """ address = None socktype = None @@ -257,7 +256,7 @@ class LoggingUtils: :return: StreamHandler, RotatingFileHandler, SysLogHandler, or TangoLoggingServiceHandler - :raises: LoggingTargetError for invalid target string + :raises LoggingTargetError: for invalid target string """ if "::" in target: target_type, target_name = target.split("::", 1) @@ -309,12 +308,20 @@ class LoggingUtils: # PROTECTED REGION END # // SKABaseDevice.additionnal_import -__all__ = ["SKABaseDevice", "main"] +__all__ = ["DeviceStateModel", "SKABaseDevice", "main"] -class SKABaseDeviceStateModel: +class DeviceStateModel: """ - Implements the state model for the SKABaseDevice + Implements the state model for the SKABaseDevice. + + This implementation contains separate state machines for adminMode + and opState. Since the two are slightly but inextricably coupled, + the opState machine includes "ADMIN" flavours for the "INIT", + "FAULT" and "DISABLED" states, to represent states where the device + has been administratively disabled via the adminModes "RESERVED", + "NOT_FITTED" and "OFFLINE". This model drives the two state machines + to ensure they remain coherent. """ def __init__(self, logger, op_state_callback=None, admin_mode_callback=None): @@ -324,11 +331,11 @@ class SKABaseDeviceStateModel: :param logger: the logger to be used by this state model. :type logger: a logger that implements the standard library logger interface - :param op_state_callback: A callback to be called when a - transition implies a change to op state + :param op_state_callback: A callback to be called when the state + machine for op_state reports a change of state :type op_state_callback: callable - :param admin_mode_callback: A callback to be called when a - transition causes a change to device admin_mode + :param admin_mode_callback: A callback to be called when the + state machine for admin_mode reports a change of state :type admin_mode_callback: callable """ self.logger = logger @@ -339,9 +346,9 @@ class SKABaseDeviceStateModel: self._op_state_callback = op_state_callback self._admin_mode_callback = admin_mode_callback - self._state_machine = BaseDeviceStateMachine( - op_state_callback=self._update_op_state, - admin_mode_callback=self._update_admin_mode + self._op_state_machine = OperationStateMachine(callback=self._update_op_state) + self._admin_mode_state_machine = AdminModeStateMachine( + callback=self._update_admin_mode ) @property @@ -354,14 +361,17 @@ class SKABaseDeviceStateModel: """ return self._admin_mode - def _update_admin_mode(self, admin_mode): + def _update_admin_mode(self, machine_state): """ - Helper method that updates admin_mode, ensuring that the callback is - called if one exists. + Helper method that updates admin_mode whenever the admin_mode + state machine reports a change of state, ensuring that the + callback is called if one exists. - :param admin_mode: the new adminMode attribute value - :type admin_mode: AdminMode + :param machine_state: the new state of the adminMode state + machine + :type machine_state: str """ + admin_mode = AdminMode[machine_state] if self._admin_mode != admin_mode: self._admin_mode = admin_mode if self._admin_mode_callback is not None: @@ -370,34 +380,98 @@ class SKABaseDeviceStateModel: @property def op_state(self): """ - Returns the op_state + Returns the op_state of this state model :returns: op_state of this state model :rtype: tango.DevState """ return self._op_state - def _update_op_state(self, op_state): - """ - Helper method that updates op_state, ensuring that the callback is - called if one exists. - - :param op_state: the new opState attribute value - :type op_state: tango.DevState - """ + _op_state_mapping = { + "INIT": DevState.INIT, + "INIT_ADMIN": DevState.INIT, + "FAULT": DevState.FAULT, + "FAULT_ADMIN": DevState.FAULT, + "DISABLE": DevState.DISABLE, + "DISABLE_ADMIN": DevState.DISABLE, + "STANDBY": DevState.STANDBY, + "OFF": DevState.OFF, + "ON": DevState.ON, + } + + def _update_op_state(self, machine_state): + """ + Helper method that updates op_state whenever the operation + state machine reports a change of state, ensuring that the + callback is called if one exists. + + :param machine_state: the new state of the operation state + machine + :type machine_state: str + """ + op_state = self._op_state_mapping[machine_state] if self._op_state != op_state: self._op_state = op_state if self._op_state_callback is not None: self._op_state_callback(op_state) + __action_breakdown = { + # "action": ("action_on_op_machine", "action_on_admin_mode_machine"), + "to_reserved": ("admin_on", "to_reserved"), + "to_notfitted": ("admin_on", "to_notfitted"), + "to_offline": ("admin_on", "to_offline"), + "to_maintenance": ("admin_off", "to_maintenance"), + "to_online": ("admin_off", "to_online"), + "init_started": ("init_started", None), + "init_succeeded_disable": ("init_succeeded_disable", None), + "init_succeeded_standby": ("init_succeeded_standby", None), + "init_succeeded_off": ("init_succeeded_off", None), + "init_failed": ("init_failed", None), + "reset_started": ("reset_started", None), + "reset_succeeded_disable": ("reset_succeeded_disable", None), + "reset_succeeded_standby": ("reset_succeeded_standby", None), + "reset_succeeded_off": ("reset_succeeded_off", None), + "reset_failed": ("reset_failed", None), + "disable_succeeded": ("disable_succeeded", None), + "disable_failed": ("disable_failed", None), + "standby_succeeded": ("standby_succeeded", None), + "standby_failed": ("standby_failed", None), + "off_succeeded": ("off_succeeded", None), + "off_failed": ("off_failed", None), + "on_succeeded": ("on_succeeded", None), + "on_failed": ("on_failed", None), + "fatal_error": ("fatal_error", None), + } + def is_action_allowed(self, action): """ Whether a given action is allowed in the current state. :param action: an action, as given in the transitions table - :type action: ANY + :type action: str + + :raises StateModelError: if the action is unknown to the state + machine + + :return: whether the action is allowed in the current state + :rtype: bool """ - return action in self._state_machine.get_triggers(self._state_machine.state) + try: + (op_action, admin_action) = self.__action_breakdown[action] + except KeyError as key_error: + raise StateModelError(key_error) + + if ( + admin_action is not None + and admin_action + not in self._admin_mode_state_machine.get_triggers( + self._admin_mode_state_machine.state + ) + ): + return False + return op_action in self._op_state_machine.get_triggers( + self._op_state_machine.state + ) def try_action(self, action): """ @@ -405,7 +479,7 @@ class SKABaseDeviceStateModel: and raises a StateModelError if it is not. :param action: an action, as given in the transitions table - :type action: ANY + :type action: str :raises StateModelError: if the action is not allowed in the current state @@ -415,7 +489,8 @@ class SKABaseDeviceStateModel: """ if not self.is_action_allowed(action): raise StateModelError( - f"Action '{action}' not allowed in current state ({self._state_machine.state})." + f"Action {action} is not allowed in operational state " + f"{self.op_state}, admin mode {self.admin_mode}." ) return True @@ -429,59 +504,62 @@ class SKABaseDeviceStateModel: current state """ - try: - self._state_machine.trigger(action) - except MachineError as error: - raise StateModelError(error) + self.try_action(action) + + (op_action, admin_action) = self.__action_breakdown[action] + + if op_action is not None: + self._op_state_machine.trigger(op_action) + if admin_action is not None: + self._admin_mode_state_machine.trigger(action) @for_testing_only - def _straight_to_state(self, state): + def _straight_to_state(self, op_state=None, admin_mode=None): """ - Takes the DeviceStateModel straight to the specified state. This method - exists to simplify testing; for example, if testing that a command may - be run in a given state, one can push the state model straight to that + Takes the DeviceStateModel straight to the specified state / mode. This + method exists to simplify testing; for example, if testing that a command + may be run in a given state, one can push the state model straight to that state, rather than having to drive it to that state through a sequence of actions. It is not intended that this method would be called outside of test setups. A warning will be raised if it is. - Note that states are non-deterministic with respect to adminMode. For - example, in state "FAULT-DISABLED", the adminMode could be OFFLINE or - NOT_FITTED. When you drive the state machine through its transitions, - the adminMode will be set accordingly. When using this method, the - adminMode will simply be set to something sensible. + Note that this method will allow you to put the device into an incoherent + combination of admin_mode and op_state (e.g. OFFLINE and ON). - :param state: the target state - :type state: string + :param op_state: the target operational state (optional) + :type op_state: :py:class:`tango.DevState` + :param admin_mode: the target admin mode (optional) + :type admin_mode: :py:class:`~ska.base.control_model.AdminMode` """ - if state == "UNINITIALISED": - pass - elif "DISABLED" in state: - if self._admin_mode not in [AdminMode.OFFLINE, AdminMode.NOT_FITTED]: - self._state_machine._update_admin_mode(AdminMode.OFFLINE) + if admin_mode is None: + admin_mode = self._admin_mode_state_machine.state else: - if self._admin_mode not in [AdminMode.ONLINE, AdminMode.MAINTENANCE]: - self._state_machine._update_admin_mode(AdminMode.ONLINE) + admin_mode = admin_mode.name - getattr(self._state_machine, f"to_{state}")() + if op_state is None: + op_state = self._op_state_machine.state + else: + op_state = op_state.name - @property - def _state(self): - """ - Returns the state of the underlying state machine. This would normally - be a hidden implementation detail, but is exposed here for testing - purposes. - """ - return self._state_machine.state + if op_state.endswith("_ADMIN"): + op_state = op_state[:-6] + if admin_mode in ["RESERVED", "NOT_FITTED", "OFFLINE"]: + op_state = f"{op_state}_ADMIN" + + getattr(self._admin_mode_state_machine, f"to_{admin_mode}")() + getattr(self._op_state_machine, f"to_{op_state}")() class SKABaseDevice(Device): """ A generic base device for SKA. """ + class InitCommand(ActionCommand): """ A class for the SKABaseDevice's init_device() "command". """ + def __init__(self, target, state_model, logger=None): """ Create a new InitCommand @@ -493,7 +571,7 @@ class SKABaseDevice(Device): :param state_model: the state model that this command uses to check that it is allowed to run, and that it drives with actions. - :type state_model: SKABaseDeviceStateModel + :type state_model: :py:class:`DeviceStateModel` :param logger: the logger to be used by this Command. If not provided, then a default module logger will be used. :type logger: a logger that implements the standard library @@ -557,6 +635,9 @@ class SKABaseDevice(Device): self.logger.info(message) return (ResultCode.OK, message) + def succeeded(self): + self.state_model.perform_action("init_succeeded_off") + _logging_config_lock = threading.Lock() _logging_configured = False @@ -723,7 +804,7 @@ class SKABaseDevice(Device): callback :param admin_mode: the new admin_mode value - :type admin_mode: AdminMode + :type admin_mode: :py:class:`~ska.base.control_model.AdminMode` """ self.push_change_event("adminMode", admin_mode) self.push_archive_event("adminMode", admin_mode) @@ -734,12 +815,10 @@ class SKABaseDevice(Device): callback :param state: the new state value - :type state: DevState + :type state: :py:class:`tango.DevState` """ if state != self.get_state(): - self.logger.info( - f"Device state changed from {self.get_state()} to {state}" - ) + self.logger.info(f"Device state changed from {self.get_state()} to {state}") self.set_state(state) self.set_status(f"The device is in {state} state.") @@ -749,11 +828,11 @@ class SKABaseDevice(Device): events are pushed. :param state: the new state - :type state: tango.DevState + :type state: :py:class:`tango.DevState` """ super().set_state(state) - self.push_change_event('state') - self.push_archive_event('state') + self.push_change_event("state") + self.push_archive_event("state") def set_status(self, status): """ @@ -764,8 +843,8 @@ class SKABaseDevice(Device): :type status: str """ super().set_status(status) - self.push_change_event('status') - self.push_archive_event('status') + self.push_change_event("status") + self.push_archive_event("status") def init_device(self): """ @@ -775,8 +854,6 @@ class SKABaseDevice(Device): default implementation of state management may leave ``init_device()`` alone. Override the ``do()`` method on the nested class ``InitCommand`` instead. - - :return: None """ try: super().init_device() @@ -801,10 +878,10 @@ class SKABaseDevice(Device): """ Creates the state model for the device """ - self.state_model = SKABaseDeviceStateModel( + self.state_model = DeviceStateModel( logger=self.logger, op_state_callback=self._update_state, - admin_mode_callback=self._update_admin_mode + admin_mode_callback=self._update_admin_mode, ) def register_command_object(self, command_name, command_object): @@ -841,20 +918,19 @@ class SKABaseDevice(Device): """ device_args = (self, self.state_model, self.logger) - self.register_command_object("On", self.OnCommand(*device_args)) + self.register_command_object("Disable", self.DisableCommand(*device_args)) + self.register_command_object("Standby", self.StandbyCommand(*device_args)) self.register_command_object("Off", self.OffCommand(*device_args)) + self.register_command_object("On", self.OnCommand(*device_args)) self.register_command_object("Reset", self.ResetCommand(*device_args)) self.register_command_object( - "GetVersionInfo", - self.GetVersionInfoCommand(*device_args) + "GetVersionInfo", self.GetVersionInfoCommand(*device_args) ) def always_executed_hook(self): # PROTECTED REGION ID(SKABaseDevice.always_executed_hook) ENABLED START # """ Method that is always executed before any device command gets executed. - - :return: None """ # PROTECTED REGION END # // SKABaseDevice.always_executed_hook @@ -862,8 +938,6 @@ class SKABaseDevice(Device): # PROTECTED REGION ID(SKABaseDevice.delete_device) ENABLED START # """ Method to cleanup when device is stopped. - - :return: None """ # PROTECTED REGION END # // SKABaseDevice.delete_device @@ -876,7 +950,7 @@ class SKABaseDevice(Device): """ Reads the Build State of the device. - :return: None + :return: the build state of the device """ return self._build_state # PROTECTED REGION END # // SKABaseDevice.buildState_read @@ -886,7 +960,7 @@ class SKABaseDevice(Device): """ Reads the Version Id of the device. - :return: None + :return: the version id of the device """ return self._version_id # PROTECTED REGION END # // SKABaseDevice.versionId_read @@ -909,7 +983,7 @@ class SKABaseDevice(Device): :param value: Logging level for logger - :return: None. + :raises LoggingLevelError: for invalid value """ try: lmc_logging_level = LoggingLevel(value) @@ -949,8 +1023,6 @@ class SKABaseDevice(Device): library defaults. :param value: Logging targets for logger - - :return: None. """ device_name = self.get_name() valid_targets = LoggingUtils.sanitise_logging_targets(value, @@ -985,9 +1057,9 @@ class SKABaseDevice(Device): Sets Admin Mode of the device. :param value: Admin Mode of the device. - :type value: AdminMode + :type value: :py:class:`~ska.base.control_model.AdminMode` - :return: None + :raises ValueError: for unknown adminMode """ if value == AdminMode.NOT_FITTED: self.state_model.perform_action("to_notfitted") @@ -997,6 +1069,8 @@ class SKABaseDevice(Device): self.state_model.perform_action("to_maintenance") elif value == AdminMode.ONLINE: self.state_model.perform_action("to_online") + elif value == AdminMode.RESERVED: + self.state_model.perform_action("to_reserved") else: raise ValueError(f"Unknown adminMode {value}") # PROTECTED REGION END # // SKABaseDevice.adminMode_write @@ -1017,8 +1091,6 @@ class SKABaseDevice(Device): Sets Control Mode of the device. :param value: Control mode value - - :return: None """ self._control_mode = value # PROTECTED REGION END # // SKABaseDevice.controlMode_write @@ -1039,8 +1111,6 @@ class SKABaseDevice(Device): Sets Simulation Mode of the device :param value: SimulationMode - - :return: None """ self._simulation_mode = value # PROTECTED REGION END # // SKABaseDevice.simulationMode_write @@ -1061,8 +1131,6 @@ class SKABaseDevice(Device): Sets Test Mode of the device. :param value: Test Mode - - :return: None """ self._test_mode = value # PROTECTED REGION END # // SKABaseDevice.testMode_write @@ -1118,7 +1186,7 @@ class SKABaseDevice(Device): :param state_model: the state model that this command uses to check that it is allowed to run, and that it drives with actions. - :type state_model: SKABaseDeviceStateModel + :type state_model: :py:class:`DeviceStateModel` :param logger: the logger to be used by this Command. If not provided, then a default module logger will be used. :type logger: a logger that implements the standard library @@ -1169,19 +1237,23 @@ class SKABaseDevice(Device): To modify behaviour for this command, modify the do() method of the command class. - :return: None + :return: A tuple containing a return code and a string + message indicating status. The message is for + information purpose only. + :rtype: (ResultCode, str) """ command = self.get_command_object("Reset") (return_code, message) = command() return [[return_code], [message]] - class OnCommand(ActionCommand): + class DisableCommand(ActionCommand): """ - A class for the SKABaseDevice's On() command. + A class for the SKABaseDevice's Disable() command. """ + def __init__(self, target, state_model, logger=None): """ - Constructor for OnCommand + Constructor for DisableCommand :param target: the object that this command acts upon; for example, the SKABaseDevice for which this class @@ -1190,37 +1262,107 @@ class SKABaseDevice(Device): :param state_model: the state model that this command uses to check that it is allowed to run, and that it drives with actions. - :type state_model: SKABaseDeviceStateModel + :type state_model: :py:class:`DeviceStateModel` :param logger: the logger to be used by this Command. If not provided, then a default module logger will be used. :type logger: a logger that implements the standard library logger interface """ - super().__init__(target, state_model, "on", logger=logger) + super().__init__(target, state_model, "disable", logger=logger) def do(self): """ - Stateless hook for On() command functionality. + Stateless hook for Disable() command functionality. :return: A tuple containing a return code and a string message indicating status. The message is for information purpose only. :rtype: (ResultCode, str) """ - message = "On command completed OK" + message = "Disable command completed OK" self.logger.info(message) return (ResultCode.OK, message) - def is_On_allowed(self): + def is_Disable_allowed(self): """ - Check if command `On` is allowed in the current device state. + Check if command Disable is allowed in the current device state. :raises ``tango.DevFailed``: if the command is not allowed :return: ``True`` if the command is allowed :rtype: boolean """ - command = self.get_command_object("On") + command = self.get_command_object("Disable") + return command.check_allowed() + + @command( + dtype_out="DevVarLongStringArray", + doc_out="(ReturnType, 'informational message')", + ) + @DebugIt() + def Disable(self): + """ + Put the device into disabled mode + + To modify behaviour for this command, modify the do() method of + the command class. + + :return: A tuple containing a return code and a string + message indicating status. The message is for + information purpose only. + :rtype: (ResultCode, str) + """ + command = self.get_command_object("Disable") + (return_code, message) = command() + return [[return_code], [message]] + + class StandbyCommand(ActionCommand): + """ + A class for the SKABaseDevice's Standby() command. + """ + + def __init__(self, target, state_model, logger=None): + """ + Constructor for StandbyCommand + + :param target: the object that this command acts upon; for + example, the SKABaseDevice for which this class + implements the command + :type target: object + :param state_model: the state model that this command uses + to check that it is allowed to run, and that it drives + with actions. + :type state_model: :py:class:`DeviceStateModel` + :param logger: the logger to be used by this Command. If not + provided, then a default module logger will be used. + :type logger: a logger that implements the standard library + logger interface + """ + super().__init__(target, state_model, "standby", logger=logger) + + def do(self): + """ + Stateless hook for Standby() command functionality. + + :return: A tuple containing a return code and a string + message indicating status. The message is for + information purpose only. + :rtype: (ResultCode, str) + """ + message = "Standby command completed OK" + self.logger.info(message) + return (ResultCode.OK, message) + + def is_Standby_allowed(self): + """ + Check if command Standby is allowed in the current device state. + + :raises ``tango.DevFailed``: if the command is not allowed + + :return: ``True`` if the command is allowed + :rtype: boolean + """ + command = self.get_command_object("Standby") return command.check_allowed() @command( @@ -1228,14 +1370,19 @@ class SKABaseDevice(Device): doc_out="(ReturnType, 'informational message')", ) @DebugIt() - def On(self): + def Standby(self): """ - Turn device on + Put the device into standby mode To modify behaviour for this command, modify the do() method of the command class. + + :return: A tuple containing a return code and a string + message indicating status. The message is for + information purpose only. + :rtype: (ResultCode, str) """ - command = self.get_command_object("On") + command = self.get_command_object("Standby") (return_code, message) = command() return [[return_code], [message]] @@ -1243,6 +1390,7 @@ class SKABaseDevice(Device): """ A class for the SKABaseDevice's Off() command. """ + def __init__(self, target, state_model, logger=None): """ Constructor for OffCommand @@ -1254,7 +1402,7 @@ class SKABaseDevice(Device): :param state_model: the state model that this command uses to check that it is allowed to run, and that it drives with actions. - :type state_model: SKABaseDeviceStateModel + :type state_model: :py:class:`DeviceStateModel` :param logger: the logger to be used by this Command. If not provided, then a default module logger will be used. :type logger: a logger that implements the standard library @@ -1298,11 +1446,86 @@ class SKABaseDevice(Device): To modify behaviour for this command, modify the do() method of the command class. + + :return: A tuple containing a return code and a string + message indicating status. The message is for + information purpose only. + :rtype: (ResultCode, str) """ command = self.get_command_object("Off") (return_code, message) = command() return [[return_code], [message]] + class OnCommand(ActionCommand): + """ + A class for the SKABaseDevice's On() command. + """ + + def __init__(self, target, state_model, logger=None): + """ + Constructor for OnCommand + + :param target: the object that this command acts upon; for + example, the SKABaseDevice for which this class + implements the command + :type target: object + :param state_model: the state model that this command uses + to check that it is allowed to run, and that it drives + with actions. + :type state_model: :py:class:`DeviceStateModel` + :param logger: the logger to be used by this Command. If not + provided, then a default module logger will be used. + :type logger: a logger that implements the standard library + logger interface + """ + super().__init__(target, state_model, "on", logger=logger) + + def do(self): + """ + Stateless hook for On() command functionality. + + :return: A tuple containing a return code and a string + message indicating status. The message is for + information purpose only. + :rtype: (ResultCode, str) + """ + message = "On command completed OK" + self.logger.info(message) + return (ResultCode.OK, message) + + def is_On_allowed(self): + """ + Check if command `On` is allowed in the current device state. + + :raises ``tango.DevFailed``: if the command is not allowed + + :return: ``True`` if the command is allowed + :rtype: boolean + """ + command = self.get_command_object("On") + return command.check_allowed() + + @command( + dtype_out="DevVarLongStringArray", + doc_out="(ReturnType, 'informational message')", + ) + @DebugIt() + def On(self): + """ + Turn device on + + To modify behaviour for this command, modify the do() method of + the command class. + + :return: A tuple containing a return code and a string + message indicating status. The message is for + information purpose only. + :rtype: (ResultCode, str) + """ + command = self.get_command_object("On") + (return_code, message) = command() + return [[return_code], [message]] + # ---------- # Run server @@ -1314,8 +1537,8 @@ def main(args=None, **kwargs): """ Main function of the SKABaseDevice module. - :param args: None - :param kwargs: + :param args: positional args to tango.server.run + :param kwargs: named args to tango.server.run """ return run((SKABaseDevice,), args=args, **kwargs) # PROTECTED REGION END # // SKABaseDevice.main diff --git a/src/ska/base/commands.py b/src/ska/base/commands.py index b8d70baa16f4d10d9fe57dfd7f0fdc9649efe2c7..e326c62c2373171c6b6b1e89b057475694e187c2 100644 --- a/src/ska/base/commands.py +++ b/src/ska/base/commands.py @@ -146,7 +146,7 @@ class BaseCommand: :param action: the action to perform on the state model :type action: string - :raises: CommandError if the action is not allowed in current state + :raises CommandError: if the action is not allowed in current state :returns: True is the action is allowed """ try: @@ -288,7 +288,7 @@ class ActionCommand(ResponseCommand): :param return_code: The return_code returned by the ``do()`` method - :type return_code: ResultCode + :type return_code: :py:class:`ResultCode` """ if return_code == ResultCode.OK: self.succeeded() diff --git a/src/ska/base/logger_device.py b/src/ska/base/logger_device.py index 72f03d2cf291bd62b2135e94257cbbe432d5a793..39a5704819d31f860c199204d20aeff89d6e9b86 100644 --- a/src/ska/base/logger_device.py +++ b/src/ska/base/logger_device.py @@ -139,7 +139,7 @@ class SKALogger(SKABaseDevice): * argin[0]: list of DevLong. Desired logging level. * argin[1]: list of DevString. Desired tango device. - :type argin: DevVarLongStringArray + :type argin: :py:class:`tango.DevVarLongStringArray` :returns: None. """ diff --git a/src/ska/base/master_device.py b/src/ska/base/master_device.py index bc5038198b293844d4717febb1fbd06811c91be2..a8e0c93a3c01690b7797e71c885628803fa5abd7 100644 --- a/src/ska/base/master_device.py +++ b/src/ska/base/master_device.py @@ -231,7 +231,7 @@ class SKAMaster(SKABaseDevice): * [nrInstances]: DevLong. Number of instances of the capability. * [Capability types]: DevString. Type of capability. - :type argin: DevVarLongStringArray. + :type argin: :py:class:`tango.DevVarLongStringArray`. :return: True if capability can be achieved, False if cannot :rtype: DevBoolean diff --git a/src/ska/base/obs_device.py b/src/ska/base/obs_device.py index 9119cc0aeb2ebc65546f8aa74325809a31db3795..b92b31c23329be84a30f58e85d205cb91953270c 100644 --- a/src/ska/base/obs_device.py +++ b/src/ska/base/obs_device.py @@ -102,7 +102,7 @@ class SKAObsDevice(SKABaseDevice): callback :param obs_state: the new obs_state value - :type obs_state: ObsState + :type admin_mode: :py:class:`~ska.base.control_model.ObsState` """ self._obs_state = obs_state self.push_change_event("obsState", obs_state) diff --git a/src/ska/base/release.py b/src/ska/base/release.py index 894fa0ecafcc8232914a3baad0729f799e6aa572..a983abcd3064aead4090cba2d4f7d79cfadaac3a 100644 --- a/src/ska/base/release.py +++ b/src/ska/base/release.py @@ -7,7 +7,7 @@ """Release information for lmc-base-classes Python Package""" name = """lmcbaseclasses""" -version = "0.6.6" +version = "0.7.0" version_info = version.split(".") description = """A set of generic base devices for SKA Telescope.""" author = "SKA India and SARAO and CSIRO" diff --git a/src/ska/base/state_machine.py b/src/ska/base/state_machine.py index 8ecbfa56912a4284c7bc686bba802d4860642458..0dc1f325762e29d9da711c10346197263adeb73c 100644 --- a/src/ska/base/state_machine.py +++ b/src/ska/base/state_machine.py @@ -2,212 +2,204 @@ This module contains specifications of SKA state machines. """ from transitions import Machine, State -from tango import DevState -from ska.base.control_model import AdminMode, ObsState +__all__ = ["OperationStateMachine", "AdminModeStateMachine", "ObservationStateMachine"] -class BaseDeviceStateMachine(Machine): + +class OperationStateMachine(Machine): """ - State machine for an SKA base device. Supports ON and OFF states, - states, plus initialisation and fault states, and - also the basic admin modes. + State machine for operational state ("opState"). + + The states supported are "UNINITIALISED", "INIT", "FAULT", + "DISABLE", "STANDBY", "OFF" and "ON". + + The states "INIT", "FAULT" and "DISABLE" also have "INIT_ADMIN", + "FAULT_ADMIN" and "DISABLE_ADMIN" flavours to represent these states + in situations where the device being modelled has been + administratively disabled. """ - def __init__(self, op_state_callback=None, admin_mode_callback=None): + def __init__(self, callback=None, **extra_kwargs): """ Initialises the state model. - :param op_state_callback: A callback to be called when a - transition implies a change to op state - :type op_state_callback: callable - :param admin_mode_callback: A callback to be called when a - transition causes a change to device admin_mode - :type admin_mode_callback: callable + :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._admin_mode = None - self._admin_mode_callback = admin_mode_callback - self._op_state = None - self._op_state_callback = op_state_callback + self._callback = callback states = [ - State("UNINITIALISED"), - State("INIT_ENABLED", on_enter="_init_entered"), - State("INIT_DISABLED", on_enter="_init_entered"), - State("FAULT_ENABLED", on_enter="_fault_entered"), - State("FAULT_DISABLED", on_enter="_fault_entered"), - State("DISABLED", on_enter="_disabled_entered"), - State("OFF", on_enter="_off_entered"), - State("ON", on_enter="_on_entered"), + "UNINITIALISED", + "INIT", + "INIT_ADMIN", + "FAULT", + "FAULT_ADMIN", + "DISABLE", + "DISABLE_ADMIN", + "STANDBY", + "OFF", + "ON", ] transitions = [ { "source": "UNINITIALISED", "trigger": "init_started", - "dest": "INIT_ENABLED", - "after": self._maintenance_callback, + "dest": "INIT", }, { - "source": ["INIT_ENABLED", "OFF", "FAULT_ENABLED", "ON"], + "source": ["INIT", "FAULT", "DISABLE", "STANDBY", "OFF", "ON"], "trigger": "fatal_error", - "dest": "FAULT_ENABLED", + "dest": "FAULT", }, { - "source": ["INIT_DISABLED", "DISABLED", "FAULT_DISABLED"], + "source": ["INIT_ADMIN", "FAULT_ADMIN", "DISABLE_ADMIN"], "trigger": "fatal_error", - "dest": "FAULT_DISABLED", + "dest": "FAULT_ADMIN", }, { - "source": "INIT_ENABLED", - "trigger": "init_succeeded", - "dest": "OFF", + "source": "INIT", + "trigger": "init_succeeded_disable", + "dest": "DISABLE", + }, + { + "source": "INIT_ADMIN", + "trigger": "init_succeeded_disable", + "dest": "DISABLE_ADMIN", }, { - "source": "INIT_DISABLED", - "trigger": "init_succeeded", - "dest": "DISABLED", + "source": "INIT", + "trigger": "init_succeeded_standby", + "dest": "STANDBY", }, { - "source": "INIT_ENABLED", + "source": "INIT", + "trigger": "init_succeeded_off", + "dest": "OFF", + }, + { + "source": "INIT", "trigger": "init_failed", - "dest": "FAULT_ENABLED", + "dest": "FAULT", }, { - "source": "INIT_DISABLED", + "source": "INIT_ADMIN", "trigger": "init_failed", - "dest": "FAULT_DISABLED", + "dest": "FAULT_ADMIN", }, { - "source": ["INIT_ENABLED", "INIT_DISABLED"], - "trigger": "to_notfitted", - "dest": "INIT_DISABLED", - "after": self._not_fitted_callback + "source": ["INIT", "INIT_ADMIN"], + "trigger": "admin_on", + "dest": "INIT_ADMIN", }, { - "source": ["INIT_ENABLED", "INIT_DISABLED"], - "trigger": "to_offline", - "dest": "INIT_DISABLED", - "after": self._offline_callback + "source": ["INIT", "INIT_ADMIN"], + "trigger": "admin_off", + "dest": "INIT", }, { - "source": ["INIT_ENABLED", "INIT_DISABLED"], - "trigger": "to_maintenance", - "dest": "INIT_ENABLED", - "after": self._maintenance_callback + "source": "FAULT", + "trigger": "reset_succeeded_disable", + "dest": "DISABLE", }, { - "source": ["INIT_ENABLED", "INIT_DISABLED"], - "trigger": "to_online", - "dest": "INIT_ENABLED", - "after": self._online_callback + "source": "FAULT_ADMIN", + "trigger": "reset_succeeded_disable", + "dest": "DISABLE_ADMIN", }, { - "source": "FAULT_DISABLED", - "trigger": "reset_succeeded", - "dest": "DISABLED", + "source": "FAULT", + "trigger": "reset_succeeded_standby", + "dest": "STANDBY", }, { - "source": "FAULT_ENABLED", - "trigger": "reset_succeeded", + "source": "FAULT", + "trigger": "reset_succeeded_off", "dest": "OFF", }, { - "source": ["FAULT_DISABLED", "FAULT_ENABLED"], + "source": "FAULT", "trigger": "reset_failed", - "dest": None, + "dest": "FAULT", }, { - "source": ["FAULT_DISABLED", "FAULT_ENABLED"], - "trigger": "to_notfitted", - "dest": "FAULT_DISABLED", - "after": self._not_fitted_callback + "source": "FAULT_ADMIN", + "trigger": "reset_failed", + "dest": "FAULT_ADMIN", }, { - "source": ["FAULT_DISABLED", "FAULT_ENABLED"], - "trigger": "to_offline", - "dest": "FAULT_DISABLED", - "after": self._offline_callback + "source": ["FAULT", "FAULT_ADMIN"], + "trigger": "admin_on", + "dest": "FAULT_ADMIN", }, { - "source": ["FAULT_DISABLED", "FAULT_ENABLED"], - "trigger": "to_maintenance", - "dest": "FAULT_ENABLED", - "after": self._maintenance_callback + "source": ["FAULT", "FAULT_ADMIN"], + "trigger": "admin_off", + "dest": "FAULT", }, { - "source": ["FAULT_DISABLED", "FAULT_ENABLED"], - "trigger": "to_online", - "dest": "FAULT_ENABLED", - "after": self._online_callback + "source": ["DISABLE", "DISABLE_ADMIN"], + "trigger": "admin_on", + "dest": "DISABLE_ADMIN", }, { - "source": "DISABLED", - "trigger": "to_notfitted", - "dest": "DISABLED", - "after": self._not_fitted_callback + "source": ["DISABLE", "DISABLE_ADMIN"], + "trigger": "admin_off", + "dest": "DISABLE", }, { - "source": "DISABLED", - "trigger": "to_offline", - "dest": "DISABLED", - "after": self._offline_callback + "source": "DISABLE_ADMIN", + "trigger": "disable_succeeded", + "dest": "DISABLE_ADMIN", }, { - "source": "DISABLED", - "trigger": "to_maintenance", - "dest": "OFF", - "after": self._maintenance_callback + "source": "DISABLE_ADMIN", + "trigger": "disable_failed", + "dest": "FAULT_ADMIN", }, { - "source": "DISABLED", - "trigger": "to_online", - "dest": "OFF", - "after": self._online_callback + "source": ["DISABLE", "STANDBY", "OFF"], + "trigger": "disable_succeeded", + "dest": "DISABLE", }, { - "source": "OFF", - "trigger": "to_notfitted", - "dest": "DISABLED", - "after": self._not_fitted_callback + "source": ["DISABLE", "STANDBY", "OFF"], + "trigger": "disable_failed", + "dest": "FAULT", }, { - "source": "OFF", - "trigger": "to_offline", - "dest": "DISABLED", - "after": self._offline_callback + "source": ["DISABLE", "STANDBY", "OFF"], + "trigger": "standby_succeeded", + "dest": "STANDBY", }, { - "source": "OFF", - "trigger": "to_maintenance", - "dest": "OFF", - "after": self._maintenance_callback + "source": ["DISABLE", "STANDBY", "OFF"], + "trigger": "standby_failed", + "dest": "FAULT", }, { - "source": "OFF", - "trigger": "to_online", + "source": ["DISABLE", "STANDBY", "OFF", "ON"], + "trigger": "off_succeeded", "dest": "OFF", - "after": self._online_callback }, { - "source": "OFF", + "source": ["DISABLE", "STANDBY", "OFF", "ON"], + "trigger": "off_failed", + "dest": "FAULT", + }, + { + "source": ["OFF", "ON"], "trigger": "on_succeeded", "dest": "ON", }, { - "source": "OFF", + "source": ["OFF", "ON"], "trigger": "on_failed", - "dest": "FAULT_ENABLED", - }, - { - "source": "ON", - "trigger": "off_succeeded", - "dest": "OFF", - }, - { - "source": "ON", - "trigger": "off_failed", - "dest": "FAULT_ENABLED", + "dest": "FAULT", }, ] @@ -215,90 +207,81 @@ class BaseDeviceStateMachine(Machine): states=states, initial="UNINITIALISED", transitions=transitions, + after_state_change=self._state_changed, + **extra_kwargs, ) - def _init_entered(self): - """ - called when the state machine enters the "" state. - """ - self._update_op_state(DevState.INIT) - - def _fault_entered(self): - """ - called when the state machine enters the "" state. + def _state_changed(self): """ - self._update_op_state(DevState.FAULT) - - def _disabled_entered(self): - """ - called when the state machine enters the "" state. - """ - self._update_op_state(DevState.DISABLE) - - def _off_entered(self): - """ - called when the state machine enters the "" state. - """ - self._update_op_state(DevState.OFF) - - def _on_entered(self): - """ - called when the state machine enters the "" state. + State machine callback that is called every time the op_state + changes. Responsible for ensuring that callbacks are called. """ - self._update_op_state(DevState.ON) + if self._callback is not None: + self._callback(self.state) - def _not_fitted_callback(self): - """ - callback called when the state machine is set to admin mode - NOT FITTED - """ - self._update_admin_mode(AdminMode.NOT_FITTED) - def _offline_callback(self): - """ - callback called when the state machine is set to admin mode - OFFLINE - """ - self._update_admin_mode(AdminMode.OFFLINE) +class AdminModeStateMachine(Machine): + """ + The state machine governing admin modes + """ - def _maintenance_callback(self): + def __init__(self, callback=None, **extra_kwargs): """ - callback called when the state machine is set to admin mode - MAINTENANCE - """ - self._update_admin_mode(AdminMode.MAINTENANCE) + Initialises the admin mode state machine model. - def _online_callback(self): - """ - callback called when the state machine is set to admin mode - online + :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._update_admin_mode(AdminMode.ONLINE) + self._callback = callback - def _update_admin_mode(self, admin_mode): - """ - Helper method: calls the admin_mode callback if one exists + states = ["RESERVED", "NOT_FITTED", "OFFLINE", "MAINTENANCE", "ONLINE"] + transitions = [ + { + "source": ["NOT_FITTED", "RESERVED", "OFFLINE"], + "trigger": "to_reserved", + "dest": "RESERVED", + }, + { + "source": ["RESERVED", "NOT_FITTED", "OFFLINE"], + "trigger": "to_notfitted", + "dest": "NOT_FITTED", + }, + { + "source": ["RESERVED", "NOT_FITTED", "OFFLINE", "MAINTENANCE", "ONLINE"], + "trigger": "to_offline", + "dest": "OFFLINE", + }, + { + "source": ["OFFLINE", "MAINTENANCE", "ONLINE"], + "trigger": "to_maintenance", + "dest": "MAINTENANCE", + }, + { + "source": ["OFFLINE", "MAINTENANCE", "ONLINE"], + "trigger": "to_online", + "dest": "ONLINE", + }, + ] - :param admin_mode: the new admin_mode value - :type admin_mode: AdminMode - """ - if self._admin_mode != admin_mode: - self._admin_mode = admin_mode - if self._admin_mode_callback is not None: - self._admin_mode_callback(self._admin_mode) + super().__init__( + states=states, + initial="MAINTENANCE", + transitions=transitions, + after_state_change=self._state_changed, + **extra_kwargs, + ) + self._state_changed() - def _update_op_state(self, op_state): + def _state_changed(self): """ - Helper method: sets this state models op_state, and calls the - op_state callback if one exists - - :param op_state: the new op state value - :type op_state: DevState + State machine callback that is called every time the admin mode + changes. Responsible for ensuring that callbacks are called. """ - if self._op_state != op_state: - self._op_state = op_state - if self._op_state_callback is not None: - self._op_state_callback(self._op_state) + if self._callback is not None: + self._callback(self.state) class ObservationStateMachine(Machine): @@ -307,164 +290,177 @@ class ObservationStateMachine(Machine): ADR-8. """ - def __init__(self, obs_state_callback=None): + def __init__(self, callback=None, **extra_kwargs): """ Initialises the model. - :param obs_state_callback: A callback to be called when a - transition causes a change to device obs_state - :type obs_state_callback: callable + :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._obs_state = ObsState.EMPTY - self._obs_state_callback = obs_state_callback + self._callback = callback - states = [obs_state.name for obs_state in ObsState] + states = [ + "EMPTY", + "RESOURCING", + "IDLE", + "CONFIGURING", + "READY", + "SCANNING", + "ABORTING", + "ABORTED", + "RESETTING", + "RESTARTING", + "FAULT", + ] transitions = [ { "source": "*", "trigger": "fatal_error", - "dest": ObsState.FAULT.name, + "dest": "FAULT", }, { - "source": [ObsState.EMPTY.name, ObsState.IDLE.name], + "source": ["EMPTY", "IDLE"], "trigger": "assign_started", - "dest": ObsState.RESOURCING.name, + "dest": "RESOURCING", }, { - "source": ObsState.IDLE.name, + "source": "IDLE", "trigger": "release_started", - "dest": ObsState.RESOURCING.name, + "dest": "RESOURCING", }, { - "source": ObsState.RESOURCING.name, + "source": "RESOURCING", "trigger": "resourcing_succeeded_some_resources", - "dest": ObsState.IDLE.name, + "dest": "IDLE", }, { - "source": ObsState.RESOURCING.name, + "source": "RESOURCING", "trigger": "resourcing_succeeded_no_resources", - "dest": ObsState.EMPTY.name, + "dest": "EMPTY", }, { - "source": ObsState.RESOURCING.name, + "source": "RESOURCING", "trigger": "resourcing_failed", - "dest": ObsState.FAULT.name, + "dest": "FAULT", }, { - "source": [ObsState.IDLE.name, ObsState.READY.name], + "source": ["IDLE", "READY"], "trigger": "configure_started", - "dest": ObsState.CONFIGURING.name, + "dest": "CONFIGURING", }, { - "source": ObsState.CONFIGURING.name, + "source": "CONFIGURING", "trigger": "configure_succeeded", - "dest": ObsState.READY.name, + "dest": "READY", }, { - "source": ObsState.CONFIGURING.name, + "source": "CONFIGURING", "trigger": "configure_failed", - "dest": ObsState.FAULT.name, + "dest": "FAULT", }, { - "source": ObsState.READY.name, + "source": "READY", "trigger": "end_succeeded", - "dest": ObsState.IDLE.name, + "dest": "IDLE", }, { - "source": ObsState.READY.name, + "source": "READY", "trigger": "end_failed", - "dest": ObsState.FAULT.name, + "dest": "FAULT", }, { - "source": ObsState.READY.name, + "source": "READY", "trigger": "scan_started", - "dest": ObsState.SCANNING.name, + "dest": "SCANNING", }, { - "source": ObsState.SCANNING.name, + "source": "SCANNING", "trigger": "scan_succeeded", - "dest": ObsState.READY.name, + "dest": "READY", }, { - "source": ObsState.SCANNING.name, + "source": "SCANNING", "trigger": "scan_failed", - "dest": ObsState.FAULT.name, + "dest": "FAULT", }, { - "source": ObsState.SCANNING.name, + "source": "SCANNING", "trigger": "end_scan_succeeded", - "dest": ObsState.READY.name, + "dest": "READY", }, { - "source": ObsState.SCANNING.name, + "source": "SCANNING", "trigger": "end_scan_failed", - "dest": ObsState.FAULT.name, + "dest": "FAULT", }, { "source": [ - ObsState.IDLE.name, ObsState.CONFIGURING.name, - ObsState.READY.name, ObsState.SCANNING.name, - ObsState.RESETTING.name, + "IDLE", + "CONFIGURING", + "READY", + "SCANNING", + "RESETTING", ], "trigger": "abort_started", - "dest": ObsState.ABORTING.name, + "dest": "ABORTING", }, { - "source": ObsState.ABORTING.name, + "source": "ABORTING", "trigger": "abort_succeeded", - "dest": ObsState.ABORTED.name, + "dest": "ABORTED", }, { - "source": ObsState.ABORTING.name, + "source": "ABORTING", "trigger": "abort_failed", - "dest": ObsState.FAULT.name, + "dest": "FAULT", }, { - "source": [ObsState.ABORTED.name, ObsState.FAULT.name], - "trigger": "obs_reset_started", - "dest": ObsState.RESETTING.name, + "source": ["ABORTED", "FAULT"], + "trigger": "reset_started", + "dest": "RESETTING", }, { - "source": ObsState.RESETTING.name, - "trigger": "obs_reset_succeeded", - "dest": ObsState.IDLE.name, + "source": "RESETTING", + "trigger": "reset_succeeded", + "dest": "IDLE", }, { - "source": ObsState.RESETTING.name, - "trigger": "obs_reset_failed", - "dest": ObsState.FAULT.name, + "source": "RESETTING", + "trigger": "reset_failed", + "dest": "FAULT", }, { - "source": [ObsState.ABORTED.name, ObsState.FAULT.name], + "source": ["ABORTED", "FAULT"], "trigger": "restart_started", - "dest": ObsState.RESTARTING.name, + "dest": "RESTARTING", }, { - "source": ObsState.RESTARTING.name, + "source": "RESTARTING", "trigger": "restart_succeeded", - "dest": ObsState.EMPTY.name, + "dest": "EMPTY", }, { - "source": ObsState.RESTARTING.name, + "source": "RESTARTING", "trigger": "restart_failed", - "dest": ObsState.FAULT.name, + "dest": "FAULT", }, ] super().__init__( states=states, - initial=ObsState.EMPTY.name, + initial="EMPTY", transitions=transitions, - after_state_change=self._obs_state_changed + after_state_change=self._state_changed, + **extra_kwargs, ) + self._state_changed() - def _obs_state_changed(self): + def _state_changed(self): """ State machine callback that is called every time the obs_state changes. Responsible for ensuring that callbacks are called. """ - obs_state = ObsState[self.state] - if self._obs_state != obs_state: - self._obs_state = obs_state - if self._obs_state_callback is not None: - self._obs_state_callback(self._obs_state) + if self._callback is not None: + self._callback(self.state) diff --git a/src/ska/base/subarray_device.py b/src/ska/base/subarray_device.py index 9a0fe49319e1973b121e67b3f3517b9bd8a557d1..b66b16c045d7e92ef7b3be2e1bc120229b488b3e 100644 --- a/src/ska/base/subarray_device.py +++ b/src/ska/base/subarray_device.py @@ -14,12 +14,12 @@ information like assigned resources, configured capabilities, etc. import json import warnings -from tango import DebugIt +from tango import DebugIt, DevState from tango.server import run, attribute, command from tango.server import device_property # SKA specific imports -from ska.base import SKAObsDevice, SKABaseDeviceStateModel +from ska.base import SKAObsDevice, DeviceStateModel from ska.base.commands import ActionCommand, ResultCode from ska.base.control_model import AdminMode, ObsState from ska.base.faults import CapabilityValidationError, StateModelError @@ -28,10 +28,10 @@ from ska.base.utils import for_testing_only # PROTECTED REGION END # // SKASubarray.additionnal_imports -__all__ = ["SKASubarray", "main"] +__all__ = ["SKASubarray", "SKASubarrayStateModel", "main"] -class SKASubarrayStateModel(SKABaseDeviceStateModel): +class SKASubarrayStateModel(DeviceStateModel): """ Implements the state model for the SKASubarray """ @@ -41,7 +41,7 @@ class SKASubarrayStateModel(SKABaseDeviceStateModel): logger, op_state_callback=None, admin_mode_callback=None, - obs_state_callback=None + obs_state_callback=None, ): """ Initialises the model. Note that this does not imply moving to @@ -66,7 +66,7 @@ class SKASubarrayStateModel(SKABaseDeviceStateModel): admin_mode_callback=admin_mode_callback, ) - self._obs_state = ObsState.EMPTY + self._obs_state = None self._obs_state_callback = obs_state_callback self._observation_state_machine = ObservationStateMachine( @@ -83,33 +83,119 @@ class SKASubarrayStateModel(SKABaseDeviceStateModel): """ return self._obs_state - def _update_obs_state(self, obs_state): + def _update_obs_state(self, machine_state): """ - Helper method that updates obs_state, ensuring that the callback - is called if one exists. + Helper method that updates obs_state whenever the observation + state machine reports a change of state, ensuring that the + callback is called if one exists. - :param obs_state: the new obsState attribute value - :type obs_state: ObsState + :param machine_state: the new state of the observation state + machine + :type machine_state: str """ + obs_state = ObsState[machine_state] if self._obs_state != obs_state: self._obs_state = obs_state if self._obs_state_callback is not None: self._obs_state_callback(obs_state) + __action_breakdown = { + # "action": ("action_on_obs_machine", "action_on_superclass"), + "off_succeeded": ("to_EMPTY", "off_succeeded"), + "off_failed": ("to_EMPTY", "off_failed"), + "on_succeeded": (None, "on_succeeded"), + "on_failed": ("to_EMPTY", "on_failed"), + "assign_started": ("assign_started", None), + "release_started": ("release_started", None), + "resourcing_succeeded_some_resources": ( + "resourcing_succeeded_some_resources", + None, + ), + "resourcing_succeeded_no_resources": ( + "resourcing_succeeded_no_resources", + None, + ), + "resourcing_failed": ("resourcing_failed", None), + "configure_started": ("configure_started", None), + "configure_succeeded": ("configure_succeeded", None), + "configure_failed": ("configure_failed", None), + "scan_started": ("scan_started", None), + "scan_succeeded": ("scan_succeeded", None), + "scan_failed": ("scan_failed", None), + "end_scan_succeeded": ("end_scan_succeeded", None), + "end_scan_failed": ("end_scan_failed", None), + "end_succeeded": ("end_succeeded", None), + "end_failed": ("end_failed", None), + "abort_started": ("abort_started", None), + "abort_succeeded": ("abort_succeeded", None), + "abort_failed": ("abort_failed", None), + "obs_reset_started": ("reset_started", None), + "obs_reset_succeeded": ("reset_succeeded", None), + "obs_reset_failed": ("reset_failed", None), + "restart_started": ("restart_started", None), + "restart_succeeded": ("restart_succeeded", None), + "restart_failed": ("restart_failed", None), + "fatal_error": ("fatal_error", None), + } + + def _is_obs_action_allowed(self, action): + if action not in self.__action_breakdown: + return None + + if self.op_state != DevState.ON: + return False + + (obs_action, super_action) = self.__action_breakdown[action] + + if obs_action not in self._observation_state_machine.get_triggers( + self._observation_state_machine.state + ): + return False + return super_action is None or super().is_action_allowed(super_action) + def is_action_allowed(self, action): """ Whether a given action is allowed in the current state. :param action: an action, as given in the transitions table :type action: ANY + + :returns: where the action is allowed in the current state: + :rtype: bool: True if the action is allowed, False if it is + not allowed + :raises StateModelError: for an unrecognised action + """ + obs_allowed = self._is_obs_action_allowed(action) + if obs_allowed is None: + return super().is_action_allowed(action) + if obs_allowed: + return True + try: + return super().is_action_allowed(action) + except StateModelError: + return False + + def try_action(self, action): """ - if self._state_machine.state == "ON": - if action in self._observation_state_machine.get_triggers( - self._observation_state_machine.state - ): - return True + Checks whether a given action is allowed in the current state, + and raises a StateModelError if it is not. - return action in self._state_machine.get_triggers(self._state_machine.state) + :param action: an action, as given in the transitions table + :type action: str + + :raises StateModelError: if the action is not allowed in the + current state + + :returns: True if the action is allowed + :rtype: boolean + """ + if not self.is_action_allowed(action): + raise StateModelError( + f"Action {action} is not allowed in operational state " + f"{self.op_state}, admin mode {self.admin_mode}, " + f"observation state {self.obs_state}." + ) + return True def perform_action(self, action): """ @@ -122,17 +208,12 @@ class SKASubarrayStateModel(SKABaseDeviceStateModel): current state """ - if self._state_machine.state == "ON": - if action in self._observation_state_machine.get_triggers( - self._observation_state_machine.state - ): - self._observation_state_machine.trigger(action) - return + self.try_action(action) - if action in self._state_machine.get_triggers( - self._state_machine.state - ): - if self._observation_state_machine.state != "EMPTY": + if self._is_obs_action_allowed(action): + (obs_action, super_action) = self.__action_breakdown[action] + + if obs_action == "to_EMPTY": message = ( "Changing device state of a non-EMPTY observing device " "should only be done as an emergency measure and may be " @@ -140,74 +221,46 @@ class SKASubarrayStateModel(SKABaseDeviceStateModel): ) self.logger.warning(message) warnings.warn(message, PendingDeprecationWarning) - self._observation_state_machine.to_EMPTY() - self._state_machine.trigger(action) - return - - raise StateModelError( - f"Action {action} is not allowed in device state " - "{self._state_machine.state}, observation_state " - "{self._observation_state_machine.state}." - ) - @for_testing_only - def _straight_to_state(self, state): - """ - Takes the DeviceStateModel straight to the specified state. This method - exists to simplify testing; for example, if testing that a command may - be run in a given state, one can push the state model straight to that - state, rather than having to drive it to that state through a sequence - of actions. It is not intended that this method would be called outside - of test setups. A warning is raised if it is. - - Note that states are non-deterministics with respect to adminMode. For - example, in state "FAULT-DISABLED", the adminMode could be OFFLINE or - NOT_FITTED. When you drive the state machine through its transitions, - the adminMode will be set accordingly. When using this method, the - adminMode will simply be set to something sensible. - - :param state: the target state - :type state: string - """ - if state == "UNINITIALISED": - pass - elif "DISABLED" in state: - if self._admin_mode not in [AdminMode.OFFLINE, AdminMode.NOT_FITTED]: - self._state_machine._update_admin_mode(AdminMode.OFFLINE) + self._observation_state_machine.trigger(obs_action) + if super_action is not None: + super().perform_action(super_action) else: - if self._admin_mode not in [AdminMode.ONLINE, AdminMode.MAINTENANCE]: - self._state_machine._update_admin_mode(AdminMode.ONLINE) + super().perform_action(action) - to_state = getattr(self._observation_state_machine, f"to_{state}", None) - if to_state is not None: - self._state_machine.to_ON() - to_state() - return - - to_state = getattr(self._state_machine, f"to_{state}", None) - if to_state is not None: - self._observation_state_machine.to_EMPTY() - to_state() - return + @for_testing_only + def _straight_to_state(self, op_state=None, admin_mode=None, obs_state=None): + """ + Takes the SKASubarrayStateModel straight to the specified states. + This method exists to simplify testing; for example, if testing + that a command may be run in a given state, one can push the + state model straight to that state, rather than having to drive + it to that state through a sequence of actions. It is not + intended that this method would be called outside of test + setups. A warning will be raised if it is. - raise StateModelError(f"No such state {state}") + Note that this method will allow you to put the device into an + incoherent combination of states and modes (e.g. adminMode + OFFLINE, opState STANDBY, and obsState SCANNING). - @property - def _state(self): + :param op_state: the target operational state (optional) + :type op_state: :py:class:`tango.DevState` + :param admin_mode: the target admin mode (optional) + :type admin_mode: :py:class:`~ska.base.control_model.AdminMode` + :param obs_state: the target observation state (optional) + :type obs_state: :py:class:`~ska.base.control_model.ObsState` """ - Returns the state of the underlying state machine. This would normally - be a hidden implementation detail, but is exposed here for testing - purposes. - """ - if self._state_machine.state == "ON": - return self._observation_state_machine.state - return self._state_machine.state + if obs_state is not None: + getattr(self._observation_state_machine, f"to_{obs_state.name}")() + super()._straight_to_state(op_state=op_state, admin_mode=admin_mode) + class SKASubarrayResourceManager: """ A simple class for managing subarray resources """ + def __init__(self): """ Constructor for SKASubarrayResourceManager @@ -277,10 +330,12 @@ class SKASubarray(SKAObsDevice): """ Implements the SKA SubArray device """ + class InitCommand(SKAObsDevice.InitCommand): """ A class for the SKASubarray's init_device() "command". """ + def do(self): """ Stateless hook for device initialisation. @@ -317,6 +372,7 @@ class SKASubarray(SKAObsDevice): """ An abstract base class for SKASubarray's resourcing commands. """ + def __init__(self, target, state_model, action_hook, logger=None): """ Constructor for _ResourcingCommand @@ -328,7 +384,7 @@ class SKASubarray(SKAObsDevice): :param state_model: the state model that this command uses to check that it is allowed to run, and that it drives with actions. - :type state_model: SKASubarrayStateModel instance + :type state_model: :py:class:`SKASubarrayStateModel` :param action_hook: a hook for the command, used to build actions that will be sent to the state model; for example, if the hook is "scan", then success of the command will @@ -365,6 +421,7 @@ class SKASubarray(SKAObsDevice): """ A class for SKASubarray's AssignResources() command. """ + def __init__(self, target, state_model, logger=None): """ Constructor for AssignResourcesCommand @@ -376,7 +433,7 @@ class SKASubarray(SKAObsDevice): :param state_model: the state model that this command uses to check that it is allowed to run, and that it drives with actions. - :type state_model: SKASubarrayStateModel instance + :type state_model: :py:class:`SKASubarrayStateModel` :param logger: the logger to be used by this Command. If not provided, then a default module logger will be used. :type logger: a logger that implements the standard library @@ -407,6 +464,7 @@ class SKASubarray(SKAObsDevice): """ A class for SKASubarray's ReleaseResources() command. """ + def __init__(self, target, state_model, logger=None): """ Constructor for OnCommand() @@ -418,7 +476,7 @@ class SKASubarray(SKAObsDevice): :param state_model: the state model that this command uses to check that it is allowed to run, and that it drives with actions. - :type state_model: SKASubarrayStateModel instance + :type state_model: :py:class:`SKASubarrayStateModel` :param logger: the logger to be used by this Command. If not provided, then a default module logger will be used. :type logger: a logger that implements the standard library @@ -449,6 +507,7 @@ class SKASubarray(SKAObsDevice): """ A class for SKASubarray's ReleaseAllResources() command. """ + def do(self): """ Stateless hook for ReleaseAllResources() command functionality. @@ -474,6 +533,7 @@ class SKASubarray(SKAObsDevice): """ A class for SKASubarray's Configure() command. """ + def __init__(self, target, state_model, logger=None): """ Constructor for ConfigureCommand @@ -485,7 +545,7 @@ class SKASubarray(SKAObsDevice): :param state_model: the state model that this command uses to check that it is allowed to run, and that it drives with actions. - :type state_model: SKASubarrayStateModel instance + :type state_model: :py:class:`SKASubarrayStateModel` :param logger: the logger to be used by this Command. If not provided, then a default module logger will be used. :type logger: a logger that implements the standard library @@ -529,6 +589,7 @@ class SKASubarray(SKAObsDevice): """ A class for SKASubarray's Scan() command. """ + def __init__(self, target, state_model, logger=None): """ Constructor for ScanCommand @@ -540,7 +601,7 @@ class SKASubarray(SKAObsDevice): :param state_model: the state model that this command uses to check that it is allowed to run, and that it drives with actions. - :type state_model: SKASubarrayStateModel + :type state_model: :py:class:`SKASubarrayStateModel` :param logger: the logger to be used by this Command. If not provided, then a default module logger will be used. :type logger: a logger that implements the standard library @@ -571,6 +632,7 @@ class SKASubarray(SKAObsDevice): """ A class for SKASubarray's EndScan() command. """ + def __init__(self, target, state_model, logger=None): """ Constructor for EndScanCommand @@ -582,7 +644,7 @@ class SKASubarray(SKAObsDevice): :param state_model: the state model that this command uses to check that it is allowed to run, and that it drives with actions. - :type state_model: SKASubarrayStateModel + :type state_model: :py:class:`SKASubarrayStateModel` :param logger: the logger to be used by this Command. If not provided, then a default module logger will be used. :type logger: a logger that implements the standard library @@ -607,6 +669,7 @@ class SKASubarray(SKAObsDevice): """ A class for SKASubarray's End() command. """ + def __init__(self, target, state_model, logger=None): """ Constructor for EndCommand @@ -618,7 +681,7 @@ class SKASubarray(SKAObsDevice): :param state_model: the state model that this command uses to check that it is allowed to run, and that it drives with actions. - :type state_model: SKASubarrayStateModel + :type state_model: :py:class:`SKASubarrayStateModel` :param logger: the logger to be used by this Command. If not provided, then a default module logger will be used. :type logger: a logger that implements the standard library @@ -646,6 +709,7 @@ class SKASubarray(SKAObsDevice): """ A class for SKASubarray's Abort() command. """ + def __init__(self, target, state_model, logger=None): """ Constructor for AbortCommand @@ -657,7 +721,7 @@ class SKASubarray(SKAObsDevice): :param state_model: the state model that this command uses to check that it is allowed to run, and that it drives with actions. - :type state_model: SKASubarrayStateModel + :type state_model: :py:class:`SKASubarrayStateModel` :param logger: the logger to be used by this Command. If not provided, then a default module logger will be used. :type logger: a logger that implements the standard library @@ -684,6 +748,7 @@ class SKASubarray(SKAObsDevice): """ A class for SKASubarray's ObsReset() command. """ + def __init__(self, target, state_model, logger=None): """ Constructor for ObsResetCommand @@ -695,7 +760,7 @@ class SKASubarray(SKAObsDevice): :param state_model: the state model that this command uses to check that it is allowed to run, and that it drives with actions. - :type state_model: SKASubarrayStateModel + :type state_model: :py:class:`SKASubarrayStateModel` :param logger: the logger to be used by this Command. If not provided, then a default module logger will be used. :type logger: a logger that implements the standard library @@ -730,6 +795,7 @@ class SKASubarray(SKAObsDevice): """ A class for SKASubarray's Restart() command. """ + def __init__(self, target, state_model, logger=None): """ Constructor for RestartCommand @@ -741,7 +807,7 @@ class SKASubarray(SKAObsDevice): :param state_model: the state model that this command uses to check that it is allowed to run, and that it drives with actions. - :type state_model: SKASubarrayStateModel + :type state_model: :py:class:`SKASubarrayStateModel` :param logger: the logger to be used by this Command. If not provided, then a default module logger will be used. :type logger: a logger that implements the standard library @@ -784,7 +850,7 @@ class SKASubarray(SKAObsDevice): logger=self.logger, op_state_callback=self._update_state, admin_mode_callback=self._update_admin_mode, - obs_state_callback=self._update_obs_state + obs_state_callback=self._update_obs_state, ) def init_command_objects(self): @@ -797,36 +863,21 @@ class SKASubarray(SKAObsDevice): resource_args = (self.resource_manager, self.state_model, self.logger) self.register_command_object( - "AssignResources", - self.AssignResourcesCommand(*resource_args) + "AssignResources", self.AssignResourcesCommand(*resource_args) ) self.register_command_object( - "ReleaseResources", - self.ReleaseResourcesCommand(*resource_args) + "ReleaseResources", self.ReleaseResourcesCommand(*resource_args) ) self.register_command_object( - "ReleaseAllResources", - self.ReleaseAllResourcesCommand(*resource_args) - ) - self.register_command_object( - "Configure", - self.ConfigureCommand(*device_args) + "ReleaseAllResources", self.ReleaseAllResourcesCommand(*resource_args) ) + self.register_command_object("Configure", self.ConfigureCommand(*device_args)) self.register_command_object("Scan", self.ScanCommand(*device_args)) - self.register_command_object( - "EndScan", - self.EndScanCommand(*device_args) - ) + self.register_command_object("EndScan", self.EndScanCommand(*device_args)) self.register_command_object("End", self.EndCommand(*device_args)) self.register_command_object("Abort", self.AbortCommand(*device_args)) - self.register_command_object( - "ObsReset", - self.ObsResetCommand(*device_args) - ) - self.register_command_object( - "Restart", - self.RestartCommand(*device_args) - ) + self.register_command_object("ObsReset", self.ObsResetCommand(*device_args)) + self.register_command_object("Restart", self.RestartCommand(*device_args)) def _validate_capability_types(self, capability_types): """ @@ -835,13 +886,13 @@ class SKASubarray(SKAObsDevice): :param device: the device for which this class implements the configure command - :type device: SKASubarray + :type device: :py:class:`SKASubarray` :param capability_types: a list strings representing capability types. :type capability_types: list - :raises ValueError: If any of the capabilities requested are - not valid. + :raises CapabilityValidationError: If any of the capabilities + requested are not valid. """ invalid_capabilities = list( set(capability_types) - set(self._configured_capabilities)) @@ -903,8 +954,6 @@ class SKASubarray(SKAObsDevice): # PROTECTED REGION ID(SKASubarray.always_executed_hook) ENABLED START # """ Method that is always executed before any device command gets executed. - - :return: None """ pass # PROTECTED REGION END # // SKASubarray.always_executed_hook @@ -913,8 +962,6 @@ class SKASubarray(SKAObsDevice): # PROTECTED REGION ID(SKASubarray.delete_device) ENABLED START # """ Method to cleanup when device is stopped. - - :return: None """ pass # PROTECTED REGION END # // SKASubarray.delete_device @@ -951,8 +998,9 @@ class SKASubarray(SKAObsDevice): in the Subarray """ configured_capabilities = [] - for capability_type, capability_instances in ( - list(self._configured_capabilities.items())): + for capability_type, capability_instances in list( + self._configured_capabilities.items() + ): configured_capabilities.append( "{}:{}".format(capability_type, capability_instances)) return sorted(configured_capabilities) @@ -991,6 +1039,11 @@ class SKASubarray(SKAObsDevice): :param argin: the resources to be assigned :type argin: list of str + + :return: A tuple containing a return code and a string + message indicating status. The message is for + information purpose only. + :rtype: (ResultCode, str) """ command = self.get_command_object("AssignResources") (return_code, message) = command(argin) @@ -1025,6 +1078,11 @@ class SKASubarray(SKAObsDevice): :param argin: the resources to be released :type argin: list of str + + :return: A tuple containing a return code and a string + message indicating status. The message is for + information purpose only. + :rtype: (ResultCode, str) """ command = self.get_command_object("ReleaseResources") (return_code, message) = command(argin) @@ -1055,8 +1113,10 @@ class SKASubarray(SKAObsDevice): To modify behaviour for this command, modify the do() method of the command class. - :return: list of resources removed - :rtype: list of string + :return: A tuple containing a return code and a string + message indicating status. The message is for + information purpose only. + :rtype: (ResultCode, str) """ command = self.get_command_object("ReleaseAllResources") (return_code, message) = command() @@ -1091,6 +1151,11 @@ class SKASubarray(SKAObsDevice): :param argin: configuration specification :type argin: string + + :return: A tuple containing a return code and a string + message indicating status. The message is for + information purpose only. + :rtype: (ResultCode, str) """ command = self.get_command_object("Configure") (return_code, message) = command(argin) @@ -1124,6 +1189,11 @@ class SKASubarray(SKAObsDevice): :param argin: Information about the scan :type argin: Array of str + + :return: A tuple containing a return code and a string + message indicating status. The message is for + information purpose only. + :rtype: (ResultCode, str) """ command = self.get_command_object("Scan") (return_code, message) = command(argin) @@ -1152,6 +1222,11 @@ class SKASubarray(SKAObsDevice): To modify behaviour for this command, modify the do() method of the command class. + + :return: A tuple containing a return code and a string + message indicating status. The message is for + information purpose only. + :rtype: (ResultCode, str) """ command = self.get_command_object("EndScan") (return_code, message) = command() @@ -1181,6 +1256,11 @@ class SKASubarray(SKAObsDevice): To modify behaviour for this command, modify the do() method of the command class. + + :return: A tuple containing a return code and a string + message indicating status. The message is for + information purpose only. + :rtype: (ResultCode, str) """ command = self.get_command_object("End") (return_code, message) = command() @@ -1210,6 +1290,11 @@ class SKASubarray(SKAObsDevice): To modify behaviour for this command, modify the do() method of the command class. + + :return: A tuple containing a return code and a string + message indicating status. The message is for + information purpose only. + :rtype: (ResultCode, str) """ command = self.get_command_object("Abort") (return_code, message) = command() @@ -1239,6 +1324,11 @@ class SKASubarray(SKAObsDevice): To modify behaviour for this command, modify the do() method of the command class. + + :return: A tuple containing a return code and a string + message indicating status. The message is for + information purpose only. + :rtype: (ResultCode, str) """ command = self.get_command_object("ObsReset") (return_code, message) = command() @@ -1269,6 +1359,11 @@ class SKASubarray(SKAObsDevice): To modify behaviour for this command, modify the do() method of the command class. + + :return: A tuple containing a return code and a string + message indicating status. The message is for + information purpose only. + :rtype: (ResultCode, str) """ command = self.get_command_object("Restart") (return_code, message) = command() @@ -1282,6 +1377,11 @@ def main(args=None, **kwargs): # PROTECTED REGION ID(SKASubarray.main) ENABLED START # """ Main entry point of the module. + + :param args: positional args to tango.server.run + :param kwargs: named args to tango.server.run + + :return: exit code """ return run((SKASubarray,), args=args, **kwargs) # PROTECTED REGION END # // SKASubarray.main diff --git a/tests/conftest.py b/tests/conftest.py index f3299213d1964c75f26dcd1fad3b324c8f712605..0c6cc46d34d701d2bbb8b68239c3356b928ad314 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,7 @@ """ A module defining a list of fixtures that are shared across all ska.base tests. """ +from collections import defaultdict import importlib import itertools import json @@ -8,9 +9,11 @@ import pytest from queue import Empty, Queue from transitions import MachineError -from tango import EventType +from tango import DevState, EventType from tango.test_context import DeviceTestContext +from ska.base.control_model import AdminMode, ObsState + def pytest_configure(config): """ @@ -31,32 +34,30 @@ def pytest_generate_tests(metafunc): will have its tests parameterised by the states and actions in the specification provided by that mark """ - # called once per each test function mark = metafunc.definition.get_closest_marker("state_machine_tester") if mark: spec = mark.args[0] - states = set() - triggers = set() - expected = {} - for (from_state, trigger, to_state) in spec: - states.add(from_state) - states.add(to_state) - triggers.add(trigger) - expected[(from_state, trigger)] = to_state + states = {state: spec["states"][state] or state for state in spec["states"]} - states = sorted(states) - triggers = sorted(triggers) + triggers = set() + expected = defaultdict(lambda: None) + for transition in spec["transitions"]: + triggers.add(transition["trigger"]) + expected[(transition["from"], transition["trigger"])] = states[transition["to"]] + test_cases = list(itertools.product(sorted(states), sorted(triggers))) + test_ids = [f"{state}-{trigger}" for (state, trigger) in test_cases] metafunc.parametrize( "state_under_test, action_under_test, expected_state", [ ( - state, + states[state], trigger, - expected[(state, trigger)] if (state, trigger) in expected else None - ) for (state, trigger) in itertools.product(states, triggers) - ] + expected[(state, trigger)] + ) for (state, trigger) in test_cases + ], + ids=test_ids ) @@ -102,10 +103,12 @@ class StateMachineTester: # Test that the action under test does what we expect it to if expected_state is None: # Action should fail and the state should not change - self.check_action_disallowed(machine, action_under_test) + assert not self.is_action_allowed(machine, action_under_test) + self.check_action_fails(machine, action_under_test) self.assert_state(machine, state_under_test) else: # Action should succeed + assert self.is_action_allowed(machine, action_under_test) self.perform_action(machine, action_under_test) self.assert_state(machine, expected_state) @@ -122,6 +125,18 @@ class StateMachineTester: """ raise NotImplementedError() + def is_action_allowed(self, machine, action): + """ + Abstract method for checking whether the action under test is + allowed from the current state. + + :param machine: the state machine under test + :type machine: state machine object instance + :param action: action to be performed on the state machine + :type action: str + """ + raise NotImplementedError() + def perform_action(self, machine, action): """ Abstract method for performing an action on the state machine @@ -133,7 +148,7 @@ class StateMachineTester: """ raise NotImplementedError() - def check_action_disallowed(self, machine, action): + def check_action_fails(self, machine, action): """ Abstract method for asserting that an action fails if performed on the state machine under test in its current state. @@ -179,6 +194,18 @@ class TransitionsStateMachineTester(StateMachineTester): """ assert machine.state == state + def is_action_allowed(self, machine, action): + """ + Check whether the action under test is allowed from the current + state. + + :param machine: the state machine under test + :type machine: state machine object instance + :param action: action to be performed on the state machine + :type action: str + """ + return action in machine.get_triggers(machine.state) + def perform_action(self, machine, action): """ Perform a given action on the state machine under test. @@ -190,9 +217,9 @@ class TransitionsStateMachineTester(StateMachineTester): """ machine.trigger(action) - def check_action_disallowed(self, machine, action): + def check_action_fails(self, machine, action): """ - Assert that performing a given action on the state maching under + Check that attempting a given action on the state machine under test fails in its current state. :param machine: the state machine under test @@ -233,6 +260,25 @@ def load_data(name): with open(f"tests/data/{name}.json", "r") as json_file: return json.load(json_file) +def load_state_machine_spec(name): + """ + Loads a state machine specification by name. + + :param name: name of the dataset to be loaded; this implementation + uses the name to find a JSON file containing the data to be + loaded. + :type name: string + """ + machine_spec = load_data(name) + for state in machine_spec["states"]: + state_spec = machine_spec["states"][state] + if "admin_mode" in state_spec: + state_spec["admin_mode"] = AdminMode[state_spec["admin_mode"]] + if "op_state" in state_spec: + state_spec["op_state"] = getattr(DevState, state_spec["op_state"]) + if "obs_state" in state_spec: + state_spec["obs_state"] = ObsState[state_spec["obs_state"]] + return machine_spec @pytest.fixture(scope="class") def tango_context(request): @@ -251,8 +297,7 @@ def tango_context(request): 'NrSubarrays': '16', 'CapabilityTypes': '', 'MaxCapabilities': ['BAND1:1', 'BAND2:1'] - }, - + }, 'SKASubarray': { "CapabilityTypes": ["BAND1", "BAND2"], 'LoggingTargetsDefault': '', @@ -319,12 +364,14 @@ def tango_change_event_helper(tango_context): state_callback.assert_calls([DevState.OFF, DevState.ON]) """ + class _Callback: """ Private callback handler class, an instance of which is returned by the tango_change_event_helper each time it is used to subscribe to a change event. """ + @staticmethod def subscribe(attribute_name): """ @@ -371,7 +418,7 @@ def tango_change_event_helper(tango_context): Event subscription callback :param event_data: data about the change events - :type event_data: tango.EventData + :type event_data: :py:class:`tango.EventData` """ if event_data.err: error = event_data.errors[0] diff --git a/tests/data/admin_mode_state_machine.json b/tests/data/admin_mode_state_machine.json new file mode 100644 index 0000000000000000000000000000000000000000..3d7e303f13fa00bf50172a1f8856459f772ab4ad --- /dev/null +++ b/tests/data/admin_mode_state_machine.json @@ -0,0 +1,96 @@ +{ + "states": { + "RESERVED": {}, + "NOT_FITTED": {}, + "OFFLINE": {}, + "MAINTENANCE": {}, + "ONLINE": {} + }, + "transitions": [ + { + "from": "NOT_FITTED", + "to": "NOT_FITTED", + "trigger": "to_notfitted" + }, + { + "from": "NOT_FITTED", + "to": "RESERVED", + "trigger": "to_reserved" + }, + { + "from": "NOT_FITTED", + "to": "OFFLINE", + "trigger": "to_offline" + }, + { + "from": "RESERVED", + "to": "NOT_FITTED", + "trigger": "to_notfitted" + }, + { + "from": "RESERVED", + "to": "RESERVED", + "trigger": "to_reserved" + }, + { + "from": "RESERVED", + "to": "OFFLINE", + "trigger": "to_offline" + }, + { + "from": "OFFLINE", + "to": "RESERVED", + "trigger": "to_reserved" + }, + { + "from": "OFFLINE", + "to": "NOT_FITTED", + "trigger": "to_notfitted" + }, + { + "from": "OFFLINE", + "to": "OFFLINE", + "trigger": "to_offline" + }, + { + "from": "OFFLINE", + "to": "MAINTENANCE", + "trigger": "to_maintenance" + }, + { + "from": "OFFLINE", + "to": "ONLINE", + "trigger": "to_online" + }, + { + "from": "MAINTENANCE", + "to": "OFFLINE", + "trigger": "to_offline" + }, + { + "from": "MAINTENANCE", + "to": "MAINTENANCE", + "trigger": "to_maintenance" + }, + { + "from": "MAINTENANCE", + "to": "ONLINE", + "trigger": "to_online" + }, + { + "from": "ONLINE", + "to": "OFFLINE", + "trigger": "to_offline" + }, + { + "from": "ONLINE", + "to": "MAINTENANCE", + "trigger": "to_maintenance" + }, + { + "from": "ONLINE", + "to": "ONLINE", + "trigger": "to_online" + } + ] +} \ No newline at end of file diff --git a/tests/data/base_device_state_machine.json b/tests/data/base_device_state_machine.json deleted file mode 100644 index a9891a842b798cb6c0ceff7fbd0c129ba9e42dd5..0000000000000000000000000000000000000000 --- a/tests/data/base_device_state_machine.json +++ /dev/null @@ -1,222 +0,0 @@ -[ - [ - "UNINITIALISED", - "init_started", - "INIT_ENABLED" - ], - [ - "INIT_ENABLED", - "to_notfitted", - "INIT_DISABLED" - ], - [ - "INIT_ENABLED", - "to_offline", - "INIT_DISABLED" - ], - [ - "INIT_ENABLED", - "to_online", - "INIT_ENABLED" - ], - [ - "INIT_ENABLED", - "to_maintenance", - "INIT_ENABLED" - ], - [ - "INIT_ENABLED", - "init_succeeded", - "OFF" - ], - [ - "INIT_ENABLED", - "init_failed", - "FAULT_ENABLED" - ], - [ - "INIT_ENABLED", - "fatal_error", - "FAULT_ENABLED" - ], - [ - "INIT_DISABLED", - "to_notfitted", - "INIT_DISABLED" - ], - [ - "INIT_DISABLED", - "to_offline", - "INIT_DISABLED" - ], - [ - "INIT_DISABLED", - "to_online", - "INIT_ENABLED" - ], - [ - "INIT_DISABLED", - "to_maintenance", - "INIT_ENABLED" - ], - [ - "INIT_DISABLED", - "init_succeeded", - "DISABLED" - ], - [ - "INIT_DISABLED", - "init_failed", - "FAULT_DISABLED" - ], - [ - "INIT_DISABLED", - "fatal_error", - "FAULT_DISABLED" - ], - [ - "FAULT_DISABLED", - "to_notfitted", - "FAULT_DISABLED" - ], - [ - "FAULT_DISABLED", - "to_offline", - "FAULT_DISABLED" - ], - [ - "FAULT_DISABLED", - "to_online", - "FAULT_ENABLED" - ], - [ - "FAULT_DISABLED", - "to_maintenance", - "FAULT_ENABLED" - ], - [ - "FAULT_DISABLED", - "reset_succeeded", - "DISABLED" - ], - [ - "FAULT_DISABLED", - "reset_failed", - "FAULT_DISABLED" - ], - [ - "FAULT_DISABLED", - "fatal_error", - "FAULT_DISABLED" - ], - [ - "FAULT_ENABLED", - "to_notfitted", - "FAULT_DISABLED" - ], - [ - "FAULT_ENABLED", - "to_offline", - "FAULT_DISABLED" - ], - [ - "FAULT_ENABLED", - "to_online", - "FAULT_ENABLED" - ], - [ - "FAULT_ENABLED", - "to_maintenance", - "FAULT_ENABLED" - ], - [ - "FAULT_ENABLED", - "reset_succeeded", - "OFF" - ], - [ - "FAULT_ENABLED", - "reset_failed", - "FAULT_ENABLED" - ], - [ - "FAULT_ENABLED", - "fatal_error", - "FAULT_ENABLED" - ], - [ - "DISABLED", - "to_notfitted", - "DISABLED" - ], - [ - "DISABLED", - "to_offline", - "DISABLED" - ], - [ - "DISABLED", - "to_online", - "OFF" - ], - [ - "DISABLED", - "to_maintenance", - "OFF" - ], - [ - "DISABLED", - "fatal_error", - "FAULT_DISABLED" - ], - [ - "OFF", - "to_notfitted", - "DISABLED" - ], - [ - "OFF", - "to_offline", - "DISABLED" - ], - [ - "OFF", - "to_online", - "OFF" - ], - [ - "OFF", - "to_maintenance", - "OFF" - ], - [ - "OFF", - "on_succeeded", - "ON" - ], - [ - "OFF", - "on_failed", - "FAULT_ENABLED" - ], - [ - "OFF", - "fatal_error", - "FAULT_ENABLED" - ], - [ - "ON", - "off_succeeded", - "OFF" - ], - [ - "ON", - "off_failed", - "FAULT_ENABLED" - ], - [ - "ON", - "fatal_error", - "FAULT_ENABLED" - ] -] \ No newline at end of file diff --git a/tests/data/device_state_machine.json b/tests/data/device_state_machine.json new file mode 100644 index 0000000000000000000000000000000000000000..544064bcc27c287d0574895a5777d7d06cf72481 --- /dev/null +++ b/tests/data/device_state_machine.json @@ -0,0 +1,860 @@ +{ + "states": { + "INIT_MAINTENANCE": { + "admin_mode": "MAINTENANCE", + "op_state": "INIT" + }, + "INIT_ONLINE": { + "admin_mode": "ONLINE", + "op_state": "INIT" + }, + "INIT_OFFLINE": { + "admin_mode": "OFFLINE", + "op_state": "INIT" + }, + "INIT_NOTFITTED": { + "admin_mode": "NOT_FITTED", + "op_state": "INIT" + }, + "INIT_RESERVED": { + "admin_mode": "RESERVED", + "op_state": "INIT" + }, + "FAULT_MAINTENANCE": { + "admin_mode": "MAINTENANCE", + "op_state": "FAULT" + }, + "FAULT_ONLINE": { + "admin_mode": "ONLINE", + "op_state": "FAULT" + }, + "FAULT_OFFLINE": { + "admin_mode": "OFFLINE", + "op_state": "FAULT" + }, + "FAULT_NOTFITTED": { + "admin_mode": "NOT_FITTED", + "op_state": "FAULT" + }, + "FAULT_RESERVED": { + "admin_mode": "RESERVED", + "op_state": "FAULT" + }, + "DISABLE_MAINTENANCE": { + "admin_mode": "MAINTENANCE", + "op_state": "DISABLE" + }, + "DISABLE_ONLINE": { + "admin_mode": "ONLINE", + "op_state": "DISABLE" + }, + "DISABLE_OFFLINE": { + "admin_mode": "OFFLINE", + "op_state": "DISABLE" + }, + "DISABLE_NOTFITTED": { + "admin_mode": "NOT_FITTED", + "op_state": "DISABLE" + }, + "DISABLE_RESERVED": { + "admin_mode": "RESERVED", + "op_state": "DISABLE" + }, + "STANDBY_MAINTENANCE": { + "admin_mode": "MAINTENANCE", + "op_state": "STANDBY" + }, + "STANDBY_ONLINE": { + "admin_mode": "ONLINE", + "op_state": "STANDBY" + }, + "OFF_MAINTENANCE": { + "admin_mode": "MAINTENANCE", + "op_state": "OFF" + }, + "OFF_ONLINE": { + "admin_mode": "ONLINE", + "op_state": "OFF" + }, + "ON_MAINTENANCE": { + "admin_mode": "MAINTENANCE", + "op_state": "ON" + }, + "ON_ONLINE": { + "admin_mode": "ONLINE", + "op_state": "ON" + } + }, + "transitions": [ + { + "from": "INIT_MAINTENANCE", + "to": "INIT_OFFLINE", + "trigger": "to_offline" + }, + { + "from": "INIT_MAINTENANCE", + "to": "INIT_MAINTENANCE", + "trigger": "to_maintenance" + }, + { + "from": "INIT_MAINTENANCE", + "to": "INIT_ONLINE", + "trigger": "to_online" + }, + { + "from": "INIT_MAINTENANCE", + "to": "DISABLE_MAINTENANCE", + "trigger": "init_succeeded_disable" + }, + { + "from": "INIT_MAINTENANCE", + "to": "STANDBY_MAINTENANCE", + "trigger": "init_succeeded_standby" + }, + { + "from": "INIT_MAINTENANCE", + "to": "OFF_MAINTENANCE", + "trigger": "init_succeeded_off" + }, + { + "from": "INIT_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "init_failed" + }, + { + "from": "INIT_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "fatal_error" + }, + { + "from": "INIT_ONLINE", + "to": "INIT_MAINTENANCE", + "trigger": "to_maintenance" + }, + { + "from": "INIT_ONLINE", + "to": "INIT_ONLINE", + "trigger": "to_online" + }, + { + "from": "INIT_ONLINE", + "to": "INIT_OFFLINE", + "trigger": "to_offline" + }, + { + "from": "INIT_ONLINE", + "to": "DISABLE_ONLINE", + "trigger": "init_succeeded_disable" + }, + { + "from": "INIT_ONLINE", + "to": "STANDBY_ONLINE", + "trigger": "init_succeeded_standby" + }, + { + "from": "INIT_ONLINE", + "to": "OFF_ONLINE", + "trigger": "init_succeeded_off" + }, + { + "from": "INIT_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "init_failed" + }, + { + "from": "INIT_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "fatal_error" + }, + { + "from": "INIT_OFFLINE", + "to": "INIT_ONLINE", + "trigger": "to_online" + }, + { + "from": "INIT_OFFLINE", + "to": "INIT_MAINTENANCE", + "trigger": "to_maintenance" + }, + { + "from": "INIT_OFFLINE", + "to": "INIT_OFFLINE", + "trigger": "to_offline" + }, + { + "from": "INIT_OFFLINE", + "to": "INIT_RESERVED", + "trigger": "to_reserved" + }, + { + "from": "INIT_OFFLINE", + "to": "INIT_NOTFITTED", + "trigger": "to_notfitted" + }, + { + "from": "INIT_OFFLINE", + "to": "DISABLE_OFFLINE", + "trigger": "init_succeeded_disable" + }, + { + "from": "INIT_OFFLINE", + "to": "FAULT_OFFLINE", + "trigger": "init_failed" + }, + { + "from": "INIT_OFFLINE", + "to": "FAULT_OFFLINE", + "trigger": "fatal_error" + }, + { + "from": "INIT_NOTFITTED", + "to": "INIT_OFFLINE", + "trigger": "to_offline" + }, + { + "from": "INIT_NOTFITTED", + "to": "INIT_RESERVED", + "trigger": "to_reserved" + }, + { + "from": "INIT_NOTFITTED", + "to": "INIT_NOTFITTED", + "trigger": "to_notfitted" + }, + { + "from": "INIT_NOTFITTED", + "to": "DISABLE_NOTFITTED", + "trigger": "init_succeeded_disable" + }, + { + "from": "INIT_NOTFITTED", + "to": "FAULT_NOTFITTED", + "trigger": "init_failed" + }, + { + "from": "INIT_NOTFITTED", + "to": "FAULT_NOTFITTED", + "trigger": "fatal_error" + }, + { + "from": "INIT_RESERVED", + "to": "INIT_NOTFITTED", + "trigger": "to_notfitted" + }, + { + "from": "INIT_RESERVED", + "to": "INIT_OFFLINE", + "trigger": "to_offline" + }, + { + "from": "INIT_RESERVED", + "to": "INIT_RESERVED", + "trigger": "to_reserved" + }, + { + "from": "INIT_RESERVED", + "to": "DISABLE_RESERVED", + "trigger": "init_succeeded_disable" + }, + { + "from": "INIT_RESERVED", + "to": "FAULT_RESERVED", + "trigger": "init_failed" + }, + { + "from": "INIT_RESERVED", + "to": "FAULT_RESERVED", + "trigger": "fatal_error" + }, + { + "from": "FAULT_MAINTENANCE", + "to": "FAULT_OFFLINE", + "trigger": "to_offline" + }, + { + "from": "FAULT_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "to_maintenance" + }, + { + "from": "FAULT_MAINTENANCE", + "to": "FAULT_ONLINE", + "trigger": "to_online" + }, + { + "from": "FAULT_MAINTENANCE", + "to": "DISABLE_MAINTENANCE", + "trigger": "reset_succeeded_disable" + }, + { + "from": "FAULT_MAINTENANCE", + "to": "STANDBY_MAINTENANCE", + "trigger": "reset_succeeded_standby" + }, + { + "from": "FAULT_MAINTENANCE", + "to": "OFF_MAINTENANCE", + "trigger": "reset_succeeded_off" + }, + { + "from": "FAULT_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "reset_failed" + }, + { + "from": "FAULT_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "fatal_error" + }, + { + "from": "FAULT_ONLINE", + "to": "FAULT_OFFLINE", + "trigger": "to_offline" + }, + { + "from": "FAULT_ONLINE", + "to": "FAULT_MAINTENANCE", + "trigger": "to_maintenance" + }, + { + "from": "FAULT_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "to_online" + }, + { + "from": "FAULT_ONLINE", + "to": "DISABLE_ONLINE", + "trigger": "reset_succeeded_disable" + }, + { + "from": "FAULT_ONLINE", + "to": "STANDBY_ONLINE", + "trigger": "reset_succeeded_standby" + }, + { + "from": "FAULT_ONLINE", + "to": "OFF_ONLINE", + "trigger": "reset_succeeded_off" + }, + { + "from": "FAULT_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "reset_failed" + }, + { + "from": "FAULT_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "fatal_error" + }, + { + "from": "FAULT_OFFLINE", + "to": "FAULT_ONLINE", + "trigger": "to_online" + }, + { + "from": "FAULT_OFFLINE", + "to": "FAULT_MAINTENANCE", + "trigger": "to_maintenance" + }, + { + "from": "FAULT_OFFLINE", + "to": "FAULT_OFFLINE", + "trigger": "to_offline" + }, + { + "from": "FAULT_OFFLINE", + "to": "FAULT_RESERVED", + "trigger": "to_reserved" + }, + { + "from": "FAULT_OFFLINE", + "to": "FAULT_NOTFITTED", + "trigger": "to_notfitted" + }, + { + "from": "FAULT_OFFLINE", + "to": "DISABLE_OFFLINE", + "trigger": "reset_succeeded_disable" + }, + { + "from": "FAULT_OFFLINE", + "to": "FAULT_OFFLINE", + "trigger": "reset_failed" + }, + { + "from": "FAULT_OFFLINE", + "to": "FAULT_OFFLINE", + "trigger": "fatal_error" + }, + { + "from": "FAULT_NOTFITTED", + "to": "FAULT_OFFLINE", + "trigger": "to_offline" + }, + { + "from": "FAULT_NOTFITTED", + "to": "FAULT_NOTFITTED", + "trigger": "to_notfitted" + }, + { + "from": "FAULT_NOTFITTED", + "to": "FAULT_RESERVED", + "trigger": "to_reserved" + }, + { + "from": "FAULT_NOTFITTED", + "to": "DISABLE_NOTFITTED", + "trigger": "reset_succeeded_disable" + }, + { + "from": "FAULT_NOTFITTED", + "to": "FAULT_NOTFITTED", + "trigger": "reset_failed" + }, + { + "from": "FAULT_NOTFITTED", + "to": "FAULT_NOTFITTED", + "trigger": "fatal_error" + }, + { + "from": "FAULT_RESERVED", + "to": "FAULT_NOTFITTED", + "trigger": "to_notfitted" + }, + { + "from": "FAULT_RESERVED", + "to": "FAULT_RESERVED", + "trigger": "to_reserved" + }, + { + "from": "FAULT_RESERVED", + "to": "FAULT_OFFLINE", + "trigger": "to_offline" + }, + { + "from": "FAULT_RESERVED", + "to": "DISABLE_RESERVED", + "trigger": "reset_succeeded_disable" + }, + { + "from": "FAULT_RESERVED", + "to": "FAULT_RESERVED", + "trigger": "reset_failed" + }, + { + "from": "FAULT_RESERVED", + "to": "FAULT_RESERVED", + "trigger": "fatal_error" + }, + { + "from": "DISABLE_MAINTENANCE", + "to": "DISABLE_OFFLINE", + "trigger": "to_offline" + }, + { + "from": "DISABLE_MAINTENANCE", + "to": "DISABLE_MAINTENANCE", + "trigger": "to_maintenance" + }, + { + "from": "DISABLE_MAINTENANCE", + "to": "DISABLE_ONLINE", + "trigger": "to_online" + }, + { + "from": "DISABLE_MAINTENANCE", + "to": "DISABLE_MAINTENANCE", + "trigger": "disable_succeeded" + }, + { + "from": "DISABLE_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "disable_failed" + }, + { + "from": "DISABLE_MAINTENANCE", + "to": "STANDBY_MAINTENANCE", + "trigger": "standby_succeeded" + }, + { + "from": "DISABLE_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "standby_failed" + }, + { + "from": "DISABLE_MAINTENANCE", + "to": "OFF_MAINTENANCE", + "trigger": "off_succeeded" + }, + { + "from": "DISABLE_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "off_failed" + }, + { + "from": "DISABLE_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "fatal_error" + }, + { + "from": "DISABLE_ONLINE", + "to": "DISABLE_OFFLINE", + "trigger": "to_offline" + }, + { + "from": "DISABLE_ONLINE", + "to": "DISABLE_MAINTENANCE", + "trigger": "to_maintenance" + }, + { + "from": "DISABLE_ONLINE", + "to": "DISABLE_ONLINE", + "trigger": "to_online" + }, + { + "from": "DISABLE_ONLINE", + "to": "DISABLE_ONLINE", + "trigger": "disable_succeeded" + }, + { + "from": "DISABLE_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "disable_failed" + }, + { + "from": "DISABLE_ONLINE", + "to": "STANDBY_ONLINE", + "trigger": "standby_succeeded" + }, + { + "from": "DISABLE_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "standby_failed" + }, + { + "from": "DISABLE_ONLINE", + "to": "OFF_ONLINE", + "trigger": "off_succeeded" + }, + { + "from": "DISABLE_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "off_failed" + }, + { + "from": "DISABLE_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "fatal_error" + }, + { + "from": "DISABLE_OFFLINE", + "to": "DISABLE_OFFLINE", + "trigger": "to_offline" + }, + { + "from": "DISABLE_OFFLINE", + "to": "DISABLE_MAINTENANCE", + "trigger": "to_maintenance" + }, + { + "from": "DISABLE_OFFLINE", + "to": "DISABLE_ONLINE", + "trigger": "to_online" + }, + { + "from": "DISABLE_OFFLINE", + "to": "DISABLE_NOTFITTED", + "trigger": "to_notfitted" + }, + { + "from": "DISABLE_OFFLINE", + "to": "DISABLE_RESERVED", + "trigger": "to_reserved" + }, + { + "from": "DISABLE_OFFLINE", + "to": "DISABLE_OFFLINE", + "trigger": "disable_succeeded" + }, + { + "from": "DISABLE_OFFLINE", + "to": "FAULT_OFFLINE", + "trigger": "disable_failed" + }, + { + "from": "DISABLE_OFFLINE", + "to": "FAULT_OFFLINE", + "trigger": "fatal_error" + }, + { + "from": "DISABLE_NOTFITTED", + "to": "DISABLE_NOTFITTED", + "trigger": "to_notfitted" + }, + { + "from": "DISABLE_NOTFITTED", + "to": "DISABLE_OFFLINE", + "trigger": "to_offline" + }, + { + "from": "DISABLE_NOTFITTED", + "to": "DISABLE_RESERVED", + "trigger": "to_reserved" + }, + { + "from": "DISABLE_NOTFITTED", + "to": "DISABLE_NOTFITTED", + "trigger": "disable_succeeded" + }, + { + "from": "DISABLE_NOTFITTED", + "to": "FAULT_NOTFITTED", + "trigger": "disable_failed" + }, + { + "from": "DISABLE_NOTFITTED", + "to": "FAULT_NOTFITTED", + "trigger": "fatal_error" + }, + { + "from": "DISABLE_RESERVED", + "to": "DISABLE_RESERVED", + "trigger": "to_reserved" + }, + { + "from": "DISABLE_RESERVED", + "to": "DISABLE_NOTFITTED", + "trigger": "to_notfitted" + }, + { + "from": "DISABLE_RESERVED", + "to": "DISABLE_OFFLINE", + "trigger": "to_offline" + }, + { + "from": "DISABLE_RESERVED", + "to": "DISABLE_RESERVED", + "trigger": "disable_succeeded" + }, + { + "from": "DISABLE_RESERVED", + "to": "FAULT_RESERVED", + "trigger": "disable_failed" + }, + { + "from": "DISABLE_RESERVED", + "to": "FAULT_RESERVED", + "trigger": "fatal_error" + }, + { + "from": "STANDBY_MAINTENANCE", + "to": "DISABLE_MAINTENANCE", + "trigger": "disable_succeeded" + }, + { + "from": "STANDBY_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "disable_failed" + }, + { + "from": "STANDBY_MAINTENANCE", + "to": "STANDBY_MAINTENANCE", + "trigger": "standby_succeeded" + }, + { + "from": "STANDBY_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "standby_failed" + }, + { + "from": "STANDBY_MAINTENANCE", + "to": "OFF_MAINTENANCE", + "trigger": "off_succeeded" + }, + { + "from": "STANDBY_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "off_failed" + }, + { + "from": "STANDBY_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "fatal_error" + }, + { + "from": "STANDBY_ONLINE", + "to": "DISABLE_ONLINE", + "trigger": "disable_succeeded" + }, + { + "from": "STANDBY_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "disable_failed" + }, + { + "from": "STANDBY_ONLINE", + "to": "STANDBY_ONLINE", + "trigger": "standby_succeeded" + }, + { + "from": "STANDBY_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "standby_failed" + }, + { + "from": "STANDBY_ONLINE", + "to": "OFF_ONLINE", + "trigger": "off_succeeded" + }, + { + "from": "STANDBY_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "off_failed" + }, + { + "from": "STANDBY_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "fatal_error" + }, + { + "from": "OFF_MAINTENANCE", + "to": "DISABLE_MAINTENANCE", + "trigger": "disable_succeeded" + }, + { + "from": "OFF_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "disable_failed" + }, + { + "from": "OFF_MAINTENANCE", + "to": "STANDBY_MAINTENANCE", + "trigger": "standby_succeeded" + }, + { + "from": "OFF_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "standby_failed" + }, + { + "from": "OFF_MAINTENANCE", + "to": "OFF_MAINTENANCE", + "trigger": "off_succeeded" + }, + { + "from": "OFF_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "off_failed" + }, + { + "from": "OFF_MAINTENANCE", + "to": "ON_MAINTENANCE", + "trigger": "on_succeeded" + }, + { + "from": "OFF_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "on_failed" + }, + { + "from": "OFF_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "fatal_error" + }, + { + "from": "OFF_ONLINE", + "to": "DISABLE_ONLINE", + "trigger": "disable_succeeded" + }, + { + "from": "OFF_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "disable_failed" + }, + { + "from": "OFF_ONLINE", + "to": "STANDBY_ONLINE", + "trigger": "standby_succeeded" + }, + { + "from": "OFF_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "standby_failed" + }, + { + "from": "OFF_ONLINE", + "to": "OFF_ONLINE", + "trigger": "off_succeeded" + }, + { + "from": "OFF_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "off_failed" + }, + { + "from": "OFF_ONLINE", + "to": "ON_ONLINE", + "trigger": "on_succeeded" + }, + { + "from": "OFF_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "on_failed" + }, + { + "from": "OFF_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "fatal_error" + }, + { + "from": "ON_MAINTENANCE", + "to": "OFF_MAINTENANCE", + "trigger": "off_succeeded" + }, + { + "from": "ON_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "off_failed" + }, + { + "from": "ON_MAINTENANCE", + "to": "ON_MAINTENANCE", + "trigger": "on_succeeded" + }, + { + "from": "ON_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "on_failed" + }, + { + "from": "ON_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "fatal_error" + }, + { + "from": "ON_ONLINE", + "to": "OFF_ONLINE", + "trigger": "off_succeeded" + }, + { + "from": "ON_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "off_failed" + }, + { + "from": "ON_ONLINE", + "to": "ON_ONLINE", + "trigger": "on_succeeded" + }, + { + "from": "ON_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "on_failed" + }, + { + "from": "ON_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "fatal_error" + } + ] +} \ No newline at end of file diff --git a/tests/data/observation_state_machine.json b/tests/data/observation_state_machine.json index 8aa6c51d39dc432157a142cba0b1191b645064b4..5315193a1a9bf9e96024cc78b683cfb7c94c33fd 100644 --- a/tests/data/observation_state_machine.json +++ b/tests/data/observation_state_machine.json @@ -1,217 +1,232 @@ -[ - [ - "EMPTY", - "assign_started", - "RESOURCING" - ], - [ - "EMPTY", - "fatal_error", - "FAULT" - ], - [ - "RESOURCING", - "resourcing_succeeded_some_resources", - "IDLE" - ], - [ - "RESOURCING", - "resourcing_succeeded_no_resources", - "EMPTY" - ], - [ - "RESOURCING", - "resourcing_failed", - "FAULT" - ], - [ - "RESOURCING", - "fatal_error", - "FAULT" - ], - [ - "IDLE", - "assign_started", - "RESOURCING" - ], - [ - "IDLE", - "release_started", - "RESOURCING" - ], - [ - "IDLE", - "configure_started", - "CONFIGURING" - ], - [ - "IDLE", - "abort_started", - "ABORTING" - ], - [ - "IDLE", - "fatal_error", - "FAULT" - ], - [ - "CONFIGURING", - "configure_succeeded", - "READY" - ], - [ - "CONFIGURING", - "configure_failed", - "FAULT" - ], - [ - "CONFIGURING", - "abort_started", - "ABORTING" - ], - [ - "CONFIGURING", - "fatal_error", - "FAULT" - ], - [ - "READY", - "end_succeeded", - "IDLE" - ], - [ - "READY", - "end_failed", - "FAULT" - ], - [ - "READY", - "configure_started", - "CONFIGURING" - ], - [ - "READY", - "abort_started", - "ABORTING" - ], - [ - "READY", - "scan_started", - "SCANNING" - ], - [ - "READY", - "fatal_error", - "FAULT" - ], - [ - "SCANNING", - "scan_succeeded", - "READY" - ], - [ - "SCANNING", - "scan_failed", - "FAULT" - ], - [ - "SCANNING", - "end_scan_succeeded", - "READY" - ], - [ - "SCANNING", - "end_scan_failed", - "FAULT" - ], - [ - "SCANNING", - "abort_started", - "ABORTING" - ], - [ - "SCANNING", - "fatal_error", - "FAULT" - ], - [ - "ABORTING", - "abort_succeeded", - "ABORTED" - ], - [ - "ABORTING", - "abort_failed", - "FAULT" - ], - [ - "ABORTING", - "fatal_error", - "FAULT" - ], - [ - "ABORTED", - "obs_reset_started", - "RESETTING" - ], - [ - "ABORTED", - "restart_started", - "RESTARTING" - ], - [ - "ABORTED", - "fatal_error", - "FAULT" - ], - [ - "FAULT", - "obs_reset_started", - "RESETTING" - ], - [ - "FAULT", - "restart_started", - "RESTARTING" - ], - [ - "FAULT", - "fatal_error", - "FAULT" - ], - [ - "RESETTING", - "obs_reset_succeeded", - "IDLE" - ], - [ - "RESETTING", - "obs_reset_failed", - "FAULT" - ], - [ - "RESETTING", - "abort_started", - "ABORTING" - ], - [ - "RESETTING", - "fatal_error", - "FAULT" - ], - [ - "RESTARTING", - "restart_succeeded", - "EMPTY" - ], - [ - "RESTARTING", - "restart_failed", - "FAULT" - ], - [ - "RESTARTING", - "fatal_error", - "FAULT" +{ + "states": { + "EMPTY": {}, + "RESOURCING": {}, + "FAULT": {}, + "IDLE": {}, + "CONFIGURING": {}, + "ABORTING": {}, + "READY": {}, + "SCANNING": {}, + "ABORTED": {}, + "RESETTING": {}, + "RESTARTING": {} + }, + "transitions": [ + { + "from": "EMPTY", + "to": "RESOURCING", + "trigger": "assign_started" + }, + { + "from": "EMPTY", + "to": "FAULT", + "trigger": "fatal_error" + }, + { + "from": "RESOURCING", + "to": "IDLE", + "trigger": "resourcing_succeeded_some_resources" + }, + { + "from": "RESOURCING", + "to": "EMPTY", + "trigger": "resourcing_succeeded_no_resources" + }, + { + "from": "RESOURCING", + "to": "FAULT", + "trigger": "resourcing_failed" + }, + { + "from": "RESOURCING", + "to": "FAULT", + "trigger": "fatal_error" + }, + { + "from": "IDLE", + "to": "RESOURCING", + "trigger": "assign_started" + }, + { + "from": "IDLE", + "to": "RESOURCING", + "trigger": "release_started" + }, + { + "from": "IDLE", + "to": "CONFIGURING", + "trigger": "configure_started" + }, + { + "from": "IDLE", + "to": "ABORTING", + "trigger": "abort_started" + }, + { + "from": "IDLE", + "to": "FAULT", + "trigger": "fatal_error" + }, + { + "from": "CONFIGURING", + "to": "READY", + "trigger": "configure_succeeded" + }, + { + "from": "CONFIGURING", + "to": "FAULT", + "trigger": "configure_failed" + }, + { + "from": "CONFIGURING", + "to": "ABORTING", + "trigger": "abort_started" + }, + { + "from": "CONFIGURING", + "to": "FAULT", + "trigger": "fatal_error" + }, + { + "from": "READY", + "to": "IDLE", + "trigger": "end_succeeded" + }, + { + "from": "READY", + "to": "FAULT", + "trigger": "end_failed" + }, + { + "from": "READY", + "to": "CONFIGURING", + "trigger": "configure_started" + }, + { + "from": "READY", + "to": "ABORTING", + "trigger": "abort_started" + }, + { + "from": "READY", + "to": "SCANNING", + "trigger": "scan_started" + }, + { + "from": "READY", + "to": "FAULT", + "trigger": "fatal_error" + }, + { + "from": "SCANNING", + "to": "READY", + "trigger": "scan_succeeded" + }, + { + "from": "SCANNING", + "to": "FAULT", + "trigger": "scan_failed" + }, + { + "from": "SCANNING", + "to": "READY", + "trigger": "end_scan_succeeded" + }, + { + "from": "SCANNING", + "to": "FAULT", + "trigger": "end_scan_failed" + }, + { + "from": "SCANNING", + "to": "ABORTING", + "trigger": "abort_started" + }, + { + "from": "SCANNING", + "to": "FAULT", + "trigger": "fatal_error" + }, + { + "from": "ABORTING", + "to": "ABORTED", + "trigger": "abort_succeeded" + }, + { + "from": "ABORTING", + "to": "FAULT", + "trigger": "abort_failed" + }, + { + "from": "ABORTING", + "to": "FAULT", + "trigger": "fatal_error" + }, + { + "from": "ABORTED", + "to": "RESETTING", + "trigger": "reset_started" + }, + { + "from": "ABORTED", + "to": "RESTARTING", + "trigger": "restart_started" + }, + { + "from": "ABORTED", + "to": "FAULT", + "trigger": "fatal_error" + }, + { + "from": "FAULT", + "to": "RESETTING", + "trigger": "reset_started" + }, + { + "from": "FAULT", + "to": "RESTARTING", + "trigger": "restart_started" + }, + { + "from": "FAULT", + "to": "FAULT", + "trigger": "fatal_error" + }, + { + "from": "RESETTING", + "to": "IDLE", + "trigger": "reset_succeeded" + }, + { + "from": "RESETTING", + "to": "FAULT", + "trigger": "reset_failed" + }, + { + "from": "RESETTING", + "to": "ABORTING", + "trigger": "abort_started" + }, + { + "from": "RESETTING", + "to": "FAULT", + "trigger": "fatal_error" + }, + { + "from": "RESTARTING", + "to": "EMPTY", + "trigger": "restart_succeeded" + }, + { + "from": "RESTARTING", + "to": "FAULT", + "trigger": "restart_failed" + }, + { + "from": "RESTARTING", + "to": "FAULT", + "trigger": "fatal_error" + } ] -] \ No newline at end of file +} \ No newline at end of file diff --git a/tests/data/operation_state_machine.json b/tests/data/operation_state_machine.json new file mode 100644 index 0000000000000000000000000000000000000000..86774be77bf13c6c47e4ba4deb90401316c4d207 --- /dev/null +++ b/tests/data/operation_state_machine.json @@ -0,0 +1,316 @@ +{ + "states": { + "UNINITIALISED": {}, + "INIT": {}, + "DISABLE": {}, + "INIT_ADMIN": {}, + "DISABLE_ADMIN": {}, + "STANDBY": {}, + "OFF": {}, + "FAULT": {}, + "FAULT_ADMIN": {}, + "ON": {} + }, + "transitions": [ + { + "from": "UNINITIALISED", + "to": "INIT", + "trigger": "init_started" + }, + { + "from": "INIT", + "to": "DISABLE", + "trigger": "init_succeeded_disable" + }, + { + "from": "INIT_ADMIN", + "to": "DISABLE_ADMIN", + "trigger": "init_succeeded_disable" + }, + { + "from": "INIT", + "to": "STANDBY", + "trigger": "init_succeeded_standby" + }, + { + "from": "INIT", + "to": "OFF", + "trigger": "init_succeeded_off" + }, + { + "from": "INIT", + "to": "FAULT", + "trigger": "init_failed" + }, + { + "from": "INIT_ADMIN", + "to": "FAULT_ADMIN", + "trigger": "init_failed" + }, + { + "from": "INIT", + "to": "FAULT", + "trigger": "fatal_error" + }, + { + "from": "INIT", + "to": "INIT_ADMIN", + "trigger": "admin_on" + }, + { + "from": "INIT", + "to": "INIT", + "trigger": "admin_off" + }, + { + "from": "INIT_ADMIN", + "to": "INIT_ADMIN", + "trigger": "admin_on" + }, + { + "from": "INIT_ADMIN", + "to": "INIT", + "trigger": "admin_off" + }, + { + "from": "INIT_ADMIN", + "to": "FAULT_ADMIN", + "trigger": "fatal_error" + }, + { + "from": "FAULT", + "to": "DISABLE", + "trigger": "reset_succeeded_disable" + }, + { + "from": "FAULT_ADMIN", + "to": "DISABLE_ADMIN", + "trigger": "reset_succeeded_disable" + }, + { + "from": "FAULT", + "to": "STANDBY", + "trigger": "reset_succeeded_standby" + }, + { + "from": "FAULT", + "to": "OFF", + "trigger": "reset_succeeded_off" + }, + { + "from": "FAULT", + "to": "FAULT", + "trigger": "reset_failed" + }, + { + "from": "FAULT_ADMIN", + "to": "FAULT_ADMIN", + "trigger": "reset_failed" + }, + { + "from": "FAULT", + "to": "FAULT", + "trigger": "fatal_error" + }, + { + "from": "FAULT_ADMIN", + "to": "FAULT_ADMIN", + "trigger": "fatal_error" + }, + { + "from": "FAULT", + "to": "FAULT_ADMIN", + "trigger": "admin_on" + }, + { + "from": "FAULT", + "to": "FAULT", + "trigger": "admin_off" + }, + { + "from": "FAULT_ADMIN", + "to": "FAULT_ADMIN", + "trigger": "admin_on" + }, + { + "from": "FAULT_ADMIN", + "to": "FAULT", + "trigger": "admin_off" + }, + { + "from": "DISABLE", + "to": "DISABLE", + "trigger": "disable_succeeded" + }, + { + "from": "DISABLE", + "to": "FAULT", + "trigger": "disable_failed" + }, + { + "from": "DISABLE", + "to": "STANDBY", + "trigger": "standby_succeeded" + }, + { + "from": "DISABLE", + "to": "FAULT", + "trigger": "standby_failed" + }, + { + "from": "DISABLE", + "to": "OFF", + "trigger": "off_succeeded" + }, + { + "from": "DISABLE", + "to": "FAULT", + "trigger": "off_failed" + }, + { + "from": "DISABLE", + "to": "FAULT", + "trigger": "fatal_error" + }, + { + "from": "DISABLE_ADMIN", + "to": "FAULT_ADMIN", + "trigger": "fatal_error" + }, + { + "from": "DISABLE", + "to": "DISABLE_ADMIN", + "trigger": "admin_on" + }, + { + "from": "DISABLE", + "to": "DISABLE", + "trigger": "admin_off" + }, + { + "from": "DISABLE_ADMIN", + "to": "DISABLE_ADMIN", + "trigger": "admin_on" + }, + { + "from": "DISABLE_ADMIN", + "to": "DISABLE", + "trigger": "admin_off" + }, + { + "from": "DISABLE_ADMIN", + "to": "DISABLE_ADMIN", + "trigger": "disable_succeeded" + }, + { + "from": "DISABLE_ADMIN", + "to": "FAULT_ADMIN", + "trigger": "disable_failed" + }, + { + "from": "STANDBY", + "to": "DISABLE", + "trigger": "disable_succeeded" + }, + { + "from": "STANDBY", + "to": "FAULT", + "trigger": "disable_failed" + }, + { + "from": "STANDBY", + "to": "STANDBY", + "trigger": "standby_succeeded" + }, + { + "from": "STANDBY", + "to": "FAULT", + "trigger": "standby_failed" + }, + { + "from": "STANDBY", + "to": "OFF", + "trigger": "off_succeeded" + }, + { + "from": "STANDBY", + "to": "FAULT", + "trigger": "off_failed" + }, + { + "from": "STANDBY", + "to": "FAULT", + "trigger": "fatal_error" + }, + { + "from": "OFF", + "to": "DISABLE", + "trigger": "disable_succeeded" + }, + { + "from": "OFF", + "to": "FAULT", + "trigger": "disable_failed" + }, + { + "from": "OFF", + "to": "STANDBY", + "trigger": "standby_succeeded" + }, + { + "from": "OFF", + "to": "FAULT", + "trigger": "standby_failed" + }, + { + "from": "OFF", + "to": "OFF", + "trigger": "off_succeeded" + }, + { + "from": "OFF", + "to": "FAULT", + "trigger": "off_failed" + }, + { + "from": "OFF", + "to": "ON", + "trigger": "on_succeeded" + }, + { + "from": "OFF", + "to": "FAULT", + "trigger": "on_failed" + }, + { + "from": "OFF", + "to": "FAULT", + "trigger": "fatal_error" + }, + { + "from": "ON", + "to": "OFF", + "trigger": "off_succeeded" + }, + { + "from": "ON", + "to": "FAULT", + "trigger": "off_failed" + }, + { + "from": "ON", + "to": "ON", + "trigger": "on_succeeded" + }, + { + "from": "ON", + "to": "FAULT", + "trigger": "on_failed" + }, + { + "from": "ON", + "to": "FAULT", + "trigger": "fatal_error" + } + ] +} \ No newline at end of file diff --git a/tests/data/subarray_state_machine.json b/tests/data/subarray_state_machine.json index 807358c8280494602ba7c2d28ccd17c90de2f1f6..57e33c3fee357176780044d46c487cfd3f7202a9 100644 --- a/tests/data/subarray_state_machine.json +++ b/tests/data/subarray_state_machine.json @@ -1,532 +1,1801 @@ -[ - [ - "UNINITIALISED", - "init_started", - "INIT_ENABLED" - ], - [ - "INIT_ENABLED", - "to_notfitted", - "INIT_DISABLED" - ], - [ - "INIT_ENABLED", - "to_offline", - "INIT_DISABLED" - ], - [ - "INIT_ENABLED", - "to_online", - "INIT_ENABLED" - ], - [ - "INIT_ENABLED", - "to_maintenance", - "INIT_ENABLED" - ], - [ - "INIT_ENABLED", - "init_succeeded", - "OFF" - ], - [ - "INIT_ENABLED", - "init_failed", - "FAULT_ENABLED" - ], - [ - "INIT_ENABLED", - "fatal_error", - "FAULT_ENABLED" - ], - [ - "INIT_DISABLED", - "to_notfitted", - "INIT_DISABLED" - ], - [ - "INIT_DISABLED", - "to_offline", - "INIT_DISABLED" - ], - [ - "INIT_DISABLED", - "to_online", - "INIT_ENABLED" - ], - [ - "INIT_DISABLED", - "to_maintenance", - "INIT_ENABLED" - ], - [ - "INIT_DISABLED", - "init_succeeded", - "DISABLED" - ], - [ - "INIT_DISABLED", - "init_failed", - "FAULT_DISABLED" - ], - [ - "INIT_DISABLED", - "fatal_error", - "FAULT_DISABLED" - ], - [ - "FAULT_DISABLED", - "to_notfitted", - "FAULT_DISABLED" - ], - [ - "FAULT_DISABLED", - "to_offline", - "FAULT_DISABLED" - ], - [ - "FAULT_DISABLED", - "to_online", - "FAULT_ENABLED" - ], - [ - "FAULT_DISABLED", - "to_maintenance", - "FAULT_ENABLED" - ], - [ - "FAULT_DISABLED", - "reset_succeeded", - "DISABLED" - ], - [ - "FAULT_DISABLED", - "reset_failed", - "FAULT_DISABLED" - ], - [ - "FAULT_DISABLED", - "fatal_error", - "FAULT_DISABLED" - ], - [ - "FAULT_ENABLED", - "to_notfitted", - "FAULT_DISABLED" - ], - [ - "FAULT_ENABLED", - "to_offline", - "FAULT_DISABLED" - ], - [ - "FAULT_ENABLED", - "to_online", - "FAULT_ENABLED" - ], - [ - "FAULT_ENABLED", - "to_maintenance", - "FAULT_ENABLED" - ], - [ - "FAULT_ENABLED", - "reset_succeeded", - "OFF" - ], - [ - "FAULT_ENABLED", - "reset_failed", - "FAULT_ENABLED" - ], - [ - "FAULT_ENABLED", - "fatal_error", - "FAULT_ENABLED" - ], - [ - "DISABLED", - "to_notfitted", - "DISABLED" - ], - [ - "DISABLED", - "to_offline", - "DISABLED" - ], - [ - "DISABLED", - "to_online", - "OFF" - ], - [ - "DISABLED", - "to_maintenance", - "OFF" - ], - [ - "DISABLED", - "fatal_error", - "FAULT_DISABLED" - ], - [ - "OFF", - "to_notfitted", - "DISABLED" - ], - [ - "OFF", - "to_offline", - "DISABLED" - ], - [ - "OFF", - "to_online", - "OFF" - ], - [ - "OFF", - "to_maintenance", - "OFF" - ], - [ - "OFF", - "on_succeeded", - "EMPTY" - ], - [ - "OFF", - "on_failed", - "FAULT_ENABLED" - ], - [ - "OFF", - "fatal_error", - "FAULT_ENABLED" - ], - [ - "EMPTY", - "off_succeeded", - "OFF" - ], - [ - "EMPTY", - "off_failed", - "FAULT_ENABLED" - ], - [ - "EMPTY", - "assign_started", - "RESOURCING" - ], - [ - "EMPTY", - "fatal_error", - "FAULT" - ], - [ - "RESOURCING", - "off_succeeded", - "OFF" - ], - [ - "RESOURCING", - "off_failed", - "FAULT_ENABLED" - ], - [ - "RESOURCING", - "resourcing_succeeded_some_resources", - "IDLE" - ], - [ - "RESOURCING", - "resourcing_succeeded_no_resources", - "EMPTY" - ], - [ - "RESOURCING", - "resourcing_failed", - "FAULT" - ], - [ - "RESOURCING", - "fatal_error", - "FAULT" - ], - [ - "IDLE", - "off_succeeded", - "OFF" - ], - [ - "IDLE", - "off_failed", - "FAULT_ENABLED" - ], - [ - "IDLE", - "assign_started", - "RESOURCING" - ], - [ - "IDLE", - "release_started", - "RESOURCING" - ], - [ - "IDLE", - "configure_started", - "CONFIGURING" - ], - [ - "IDLE", - "abort_started", - "ABORTING" - ], - [ - "IDLE", - "fatal_error", - "FAULT" - ], - [ - "CONFIGURING", - "off_succeeded", - "OFF" - ], - [ - "CONFIGURING", - "off_failed", - "FAULT_ENABLED" - ], - [ - "CONFIGURING", - "configure_succeeded", - "READY" - ], - [ - "CONFIGURING", - "configure_failed", - "FAULT" - ], - [ - "CONFIGURING", - "abort_started", - "ABORTING" - ], - [ - "CONFIGURING", - "fatal_error", - "FAULT" - ], - [ - "READY", - "off_succeeded", - "OFF" - ], - [ - "READY", - "off_failed", - "FAULT_ENABLED" - ], - [ - "READY", - "end_succeeded", - "IDLE" - ], - [ - "READY", - "end_failed", - "FAULT" - ], - [ - "READY", - "configure_started", - "CONFIGURING" - ], - [ - "READY", - "abort_started", - "ABORTING" - ], - [ - "READY", - "scan_started", - "SCANNING" - ], - [ - "READY", - "fatal_error", - "FAULT" - ], - [ - "SCANNING", - "off_succeeded", - "OFF" - ], - [ - "SCANNING", - "off_failed", - "FAULT_ENABLED" - ], - [ - "SCANNING", - "scan_succeeded", - "READY" - ], - [ - "SCANNING", - "scan_failed", - "FAULT" - ], - [ - "SCANNING", - "end_scan_succeeded", - "READY" - ], - [ - "SCANNING", - "end_scan_failed", - "FAULT" - ], - [ - "SCANNING", - "abort_started", - "ABORTING" - ], - [ - "SCANNING", - "fatal_error", - "FAULT" - ], - [ - "ABORTING", - "off_succeeded", - "OFF" - ], - [ - "ABORTING", - "off_failed", - "FAULT_ENABLED" - ], - [ - "ABORTING", - "abort_succeeded", - "ABORTED" - ], - [ - "ABORTING", - "abort_failed", - "FAULT" - ], - [ - "ABORTING", - "fatal_error", - "FAULT" - ], - [ - "ABORTED", - "off_succeeded", - "OFF" - ], - [ - "ABORTED", - "off_failed", - "FAULT_ENABLED" - ], - [ - "ABORTED", - "obs_reset_started", - "RESETTING" - ], - [ - "ABORTED", - "restart_started", - "RESTARTING" - ], - [ - "ABORTED", - "fatal_error", - "FAULT" - ], - [ - "FAULT", - "off_succeeded", - "OFF" - ], - [ - "FAULT", - "off_failed", - "FAULT_ENABLED" - ], - [ - "FAULT", - "obs_reset_started", - "RESETTING" - ], - [ - "FAULT", - "restart_started", - "RESTARTING" - ], - [ - "FAULT", - "fatal_error", - "FAULT" - ], - [ - "RESETTING", - "off_succeeded", - "OFF" - ], - [ - "RESETTING", - "off_failed", - "FAULT_ENABLED" - ], - [ - "RESETTING", - "obs_reset_succeeded", - "IDLE" - ], - [ - "RESETTING", - "obs_reset_failed", - "FAULT" - ], - [ - "RESETTING", - "abort_started", - "ABORTING" - ], - [ - "RESETTING", - "fatal_error", - "FAULT" - ], - [ - "RESTARTING", - "off_succeeded", - "OFF" - ], - [ - "RESTARTING", - "off_failed", - "FAULT_ENABLED" - ], - [ - "RESTARTING", - "restart_succeeded", - "EMPTY" - ], - [ - "RESTARTING", - "restart_failed", - "FAULT" - ], - [ - "RESTARTING", - "fatal_error", - "FAULT" +{ + "states": { + "INIT_MAINTENANCE": { + "admin_mode": "MAINTENANCE", + "op_state": "INIT", + "obs_state": "EMPTY" + }, + "INIT_ONLINE": { + "admin_mode": "ONLINE", + "op_state": "INIT", + "obs_state": "EMPTY" + }, + "INIT_OFFLINE": { + "admin_mode": "OFFLINE", + "op_state": "INIT", + "obs_state": "EMPTY" + }, + "INIT_NOTFITTED": { + "admin_mode": "NOT_FITTED", + "op_state": "INIT", + "obs_state": "EMPTY" + }, + "INIT_RESERVED": { + "admin_mode": "RESERVED", + "op_state": "INIT", + "obs_state": "EMPTY" + }, + "FAULT_MAINTENANCE": { + "admin_mode": "MAINTENANCE", + "op_state": "FAULT", + "obs_state": "EMPTY" + }, + "FAULT_ONLINE": { + "admin_mode": "ONLINE", + "op_state": "FAULT", + "obs_state": "EMPTY" + }, + "FAULT_OFFLINE": { + "admin_mode": "OFFLINE", + "op_state": "FAULT", + "obs_state": "EMPTY" + }, + "FAULT_NOTFITTED": { + "admin_mode": "NOT_FITTED", + "op_state": "FAULT", + "obs_state": "EMPTY" + }, + "FAULT_RESERVED": { + "admin_mode": "RESERVED", + "op_state": "FAULT", + "obs_state": "EMPTY" + }, + "DISABLE_MAINTENANCE": { + "admin_mode": "MAINTENANCE", + "op_state": "DISABLE", + "obs_state": "EMPTY" + }, + "DISABLE_ONLINE": { + "admin_mode": "ONLINE", + "op_state": "DISABLE", + "obs_state": "EMPTY" + }, + "DISABLE_OFFLINE": { + "admin_mode": "OFFLINE", + "op_state": "DISABLE", + "obs_state": "EMPTY" + }, + "DISABLE_NOTFITTED": { + "admin_mode": "NOT_FITTED", + "op_state": "DISABLE", + "obs_state": "EMPTY" + }, + "DISABLE_RESERVED": { + "admin_mode": "RESERVED", + "op_state": "DISABLE", + "obs_state": "EMPTY" + }, + "STANDBY_MAINTENANCE": { + "admin_mode": "MAINTENANCE", + "op_state": "STANDBY", + "obs_state": "EMPTY" + }, + "STANDBY_ONLINE": { + "admin_mode": "ONLINE", + "op_state": "STANDBY", + "obs_state": "EMPTY" + }, + "OFF_MAINTENANCE": { + "admin_mode": "MAINTENANCE", + "op_state": "OFF", + "obs_state": "EMPTY" + }, + "OFF_ONLINE": { + "admin_mode": "ONLINE", + "op_state": "OFF", + "obs_state": "EMPTY" + }, + "EMPTY_MAINTENANCE": { + "admin_mode": "MAINTENANCE", + "op_state": "ON", + "obs_state": "EMPTY" + }, + "EMPTY_ONLINE": { + "admin_mode": "ONLINE", + "op_state": "ON", + "obs_state": "EMPTY" + }, + "RESOURCING_MAINTENANCE": { + "admin_mode": "MAINTENANCE", + "op_state": "ON", + "obs_state": "RESOURCING" + }, + "RESOURCING_ONLINE": { + "admin_mode": "ONLINE", + "op_state": "ON", + "obs_state": "RESOURCING" + }, + "IDLE_MAINTENANCE": { + "admin_mode": "MAINTENANCE", + "op_state": "ON", + "obs_state": "IDLE" + }, + "IDLE_ONLINE": { + "admin_mode": "ONLINE", + "op_state": "ON", + "obs_state": "IDLE" + }, + "CONFIGURING_MAINTENANCE": { + "admin_mode": "MAINTENANCE", + "op_state": "ON", + "obs_state": "CONFIGURING" + }, + "CONFIGURING_ONLINE": { + "admin_mode": "ONLINE", + "op_state": "ON", + "obs_state": "CONFIGURING" + }, + "READY_MAINTENANCE": { + "admin_mode": "MAINTENANCE", + "op_state": "ON", + "obs_state": "READY" + }, + "READY_ONLINE": { + "admin_mode": "ONLINE", + "op_state": "ON", + "obs_state": "READY" + }, + "SCANNING_MAINTENANCE": { + "admin_mode": "MAINTENANCE", + "op_state": "ON", + "obs_state": "SCANNING" + }, + "SCANNING_ONLINE": { + "admin_mode": "ONLINE", + "op_state": "ON", + "obs_state": "SCANNING" + }, + "ABORTING_MAINTENANCE": { + "admin_mode": "MAINTENANCE", + "op_state": "ON", + "obs_state": "ABORTING" + }, + "ABORTING_ONLINE": { + "admin_mode": "ONLINE", + "op_state": "ON", + "obs_state": "ABORTING" + }, + "ABORTED_MAINTENANCE": { + "admin_mode": "MAINTENANCE", + "op_state": "ON", + "obs_state": "ABORTED" + }, + "ABORTED_ONLINE": { + "admin_mode": "ONLINE", + "op_state": "ON", + "obs_state": "ABORTED" + }, + "OBSFAULT_MAINTENANCE": { + "admin_mode": "MAINTENANCE", + "op_state": "ON", + "obs_state": "FAULT" + }, + "OBSFAULT_ONLINE": { + "admin_mode": "ONLINE", + "op_state": "ON", + "obs_state": "FAULT" + }, + "RESETTING_MAINTENANCE": { + "admin_mode": "MAINTENANCE", + "op_state": "ON", + "obs_state": "RESETTING" + }, + "RESETTING_ONLINE": { + "admin_mode": "ONLINE", + "op_state": "ON", + "obs_state": "RESETTING" + }, + "RESTARTING_MAINTENANCE": { + "admin_mode": "MAINTENANCE", + "op_state": "ON", + "obs_state": "RESTARTING" + }, + "RESTARTING_ONLINE": { + "admin_mode": "ONLINE", + "op_state": "ON", + "obs_state": "RESTARTING" + } + }, + "transitions": [ + { + "from": "INIT_MAINTENANCE", + "to": "INIT_OFFLINE", + "trigger": "to_offline" + }, + { + "from": "INIT_MAINTENANCE", + "to": "INIT_MAINTENANCE", + "trigger": "to_maintenance" + }, + { + "from": "INIT_MAINTENANCE", + "to": "INIT_ONLINE", + "trigger": "to_online" + }, + { + "from": "INIT_MAINTENANCE", + "to": "DISABLE_MAINTENANCE", + "trigger": "init_succeeded_disable" + }, + { + "from": "INIT_MAINTENANCE", + "to": "STANDBY_MAINTENANCE", + "trigger": "init_succeeded_standby" + }, + { + "from": "INIT_MAINTENANCE", + "to": "OFF_MAINTENANCE", + "trigger": "init_succeeded_off" + }, + { + "from": "INIT_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "init_failed" + }, + { + "from": "INIT_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "fatal_error" + }, + { + "from": "INIT_ONLINE", + "to": "INIT_MAINTENANCE", + "trigger": "to_maintenance" + }, + { + "from": "INIT_ONLINE", + "to": "INIT_ONLINE", + "trigger": "to_online" + }, + { + "from": "INIT_ONLINE", + "to": "INIT_OFFLINE", + "trigger": "to_offline" + }, + { + "from": "INIT_ONLINE", + "to": "DISABLE_ONLINE", + "trigger": "init_succeeded_disable" + }, + { + "from": "INIT_ONLINE", + "to": "STANDBY_ONLINE", + "trigger": "init_succeeded_standby" + }, + { + "from": "INIT_ONLINE", + "to": "OFF_ONLINE", + "trigger": "init_succeeded_off" + }, + { + "from": "INIT_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "init_failed" + }, + { + "from": "INIT_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "fatal_error" + }, + { + "from": "INIT_OFFLINE", + "to": "INIT_ONLINE", + "trigger": "to_online" + }, + { + "from": "INIT_OFFLINE", + "to": "INIT_MAINTENANCE", + "trigger": "to_maintenance" + }, + { + "from": "INIT_OFFLINE", + "to": "INIT_OFFLINE", + "trigger": "to_offline" + }, + { + "from": "INIT_OFFLINE", + "to": "INIT_RESERVED", + "trigger": "to_reserved" + }, + { + "from": "INIT_OFFLINE", + "to": "INIT_NOTFITTED", + "trigger": "to_notfitted" + }, + { + "from": "INIT_OFFLINE", + "to": "DISABLE_OFFLINE", + "trigger": "init_succeeded_disable" + }, + { + "from": "INIT_OFFLINE", + "to": "FAULT_OFFLINE", + "trigger": "init_failed" + }, + { + "from": "INIT_OFFLINE", + "to": "FAULT_OFFLINE", + "trigger": "fatal_error" + }, + { + "from": "INIT_NOTFITTED", + "to": "INIT_OFFLINE", + "trigger": "to_offline" + }, + { + "from": "INIT_NOTFITTED", + "to": "INIT_RESERVED", + "trigger": "to_reserved" + }, + { + "from": "INIT_NOTFITTED", + "to": "INIT_NOTFITTED", + "trigger": "to_notfitted" + }, + { + "from": "INIT_NOTFITTED", + "to": "DISABLE_NOTFITTED", + "trigger": "init_succeeded_disable" + }, + { + "from": "INIT_NOTFITTED", + "to": "FAULT_NOTFITTED", + "trigger": "init_failed" + }, + { + "from": "INIT_NOTFITTED", + "to": "FAULT_NOTFITTED", + "trigger": "fatal_error" + }, + { + "from": "INIT_RESERVED", + "to": "INIT_NOTFITTED", + "trigger": "to_notfitted" + }, + { + "from": "INIT_RESERVED", + "to": "INIT_OFFLINE", + "trigger": "to_offline" + }, + { + "from": "INIT_RESERVED", + "to": "INIT_RESERVED", + "trigger": "to_reserved" + }, + { + "from": "INIT_RESERVED", + "to": "DISABLE_RESERVED", + "trigger": "init_succeeded_disable" + }, + { + "from": "INIT_RESERVED", + "to": "FAULT_RESERVED", + "trigger": "init_failed" + }, + { + "from": "INIT_RESERVED", + "to": "FAULT_RESERVED", + "trigger": "fatal_error" + }, + { + "from": "FAULT_MAINTENANCE", + "to": "FAULT_OFFLINE", + "trigger": "to_offline" + }, + { + "from": "FAULT_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "to_maintenance" + }, + { + "from": "FAULT_MAINTENANCE", + "to": "FAULT_ONLINE", + "trigger": "to_online" + }, + { + "from": "FAULT_MAINTENANCE", + "to": "DISABLE_MAINTENANCE", + "trigger": "reset_succeeded_disable" + }, + { + "from": "FAULT_MAINTENANCE", + "to": "STANDBY_MAINTENANCE", + "trigger": "reset_succeeded_standby" + }, + { + "from": "FAULT_MAINTENANCE", + "to": "OFF_MAINTENANCE", + "trigger": "reset_succeeded_off" + }, + { + "from": "FAULT_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "reset_failed" + }, + { + "from": "FAULT_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "fatal_error" + }, + { + "from": "FAULT_ONLINE", + "to": "FAULT_OFFLINE", + "trigger": "to_offline" + }, + { + "from": "FAULT_ONLINE", + "to": "FAULT_MAINTENANCE", + "trigger": "to_maintenance" + }, + { + "from": "FAULT_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "to_online" + }, + { + "from": "FAULT_ONLINE", + "to": "DISABLE_ONLINE", + "trigger": "reset_succeeded_disable" + }, + { + "from": "FAULT_ONLINE", + "to": "STANDBY_ONLINE", + "trigger": "reset_succeeded_standby" + }, + { + "from": "FAULT_ONLINE", + "to": "OFF_ONLINE", + "trigger": "reset_succeeded_off" + }, + { + "from": "FAULT_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "reset_failed" + }, + { + "from": "FAULT_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "fatal_error" + }, + { + "from": "FAULT_OFFLINE", + "to": "FAULT_ONLINE", + "trigger": "to_online" + }, + { + "from": "FAULT_OFFLINE", + "to": "FAULT_MAINTENANCE", + "trigger": "to_maintenance" + }, + { + "from": "FAULT_OFFLINE", + "to": "FAULT_OFFLINE", + "trigger": "to_offline" + }, + { + "from": "FAULT_OFFLINE", + "to": "FAULT_RESERVED", + "trigger": "to_reserved" + }, + { + "from": "FAULT_OFFLINE", + "to": "FAULT_NOTFITTED", + "trigger": "to_notfitted" + }, + { + "from": "FAULT_OFFLINE", + "to": "DISABLE_OFFLINE", + "trigger": "reset_succeeded_disable" + }, + { + "from": "FAULT_OFFLINE", + "to": "FAULT_OFFLINE", + "trigger": "reset_failed" + }, + { + "from": "FAULT_OFFLINE", + "to": "FAULT_OFFLINE", + "trigger": "fatal_error" + }, + { + "from": "FAULT_NOTFITTED", + "to": "FAULT_OFFLINE", + "trigger": "to_offline" + }, + { + "from": "FAULT_NOTFITTED", + "to": "FAULT_NOTFITTED", + "trigger": "to_notfitted" + }, + { + "from": "FAULT_NOTFITTED", + "to": "FAULT_RESERVED", + "trigger": "to_reserved" + }, + { + "from": "FAULT_NOTFITTED", + "to": "DISABLE_NOTFITTED", + "trigger": "reset_succeeded_disable" + }, + { + "from": "FAULT_NOTFITTED", + "to": "FAULT_NOTFITTED", + "trigger": "reset_failed" + }, + { + "from": "FAULT_NOTFITTED", + "to": "FAULT_NOTFITTED", + "trigger": "fatal_error" + }, + { + "from": "FAULT_RESERVED", + "to": "FAULT_NOTFITTED", + "trigger": "to_notfitted" + }, + { + "from": "FAULT_RESERVED", + "to": "FAULT_RESERVED", + "trigger": "to_reserved" + }, + { + "from": "FAULT_RESERVED", + "to": "FAULT_OFFLINE", + "trigger": "to_offline" + }, + { + "from": "FAULT_RESERVED", + "to": "DISABLE_RESERVED", + "trigger": "reset_succeeded_disable" + }, + { + "from": "FAULT_RESERVED", + "to": "FAULT_RESERVED", + "trigger": "reset_failed" + }, + { + "from": "FAULT_RESERVED", + "to": "FAULT_RESERVED", + "trigger": "fatal_error" + }, + { + "from": "DISABLE_MAINTENANCE", + "to": "DISABLE_OFFLINE", + "trigger": "to_offline" + }, + { + "from": "DISABLE_MAINTENANCE", + "to": "DISABLE_MAINTENANCE", + "trigger": "to_maintenance" + }, + { + "from": "DISABLE_MAINTENANCE", + "to": "DISABLE_ONLINE", + "trigger": "to_online" + }, + { + "from": "DISABLE_MAINTENANCE", + "to": "DISABLE_MAINTENANCE", + "trigger": "disable_succeeded" + }, + { + "from": "DISABLE_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "disable_failed" + }, + { + "from": "DISABLE_MAINTENANCE", + "to": "STANDBY_MAINTENANCE", + "trigger": "standby_succeeded" + }, + { + "from": "DISABLE_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "standby_failed" + }, + { + "from": "DISABLE_MAINTENANCE", + "to": "OFF_MAINTENANCE", + "trigger": "off_succeeded" + }, + { + "from": "DISABLE_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "off_failed" + }, + { + "from": "DISABLE_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "fatal_error" + }, + { + "from": "DISABLE_ONLINE", + "to": "DISABLE_OFFLINE", + "trigger": "to_offline" + }, + { + "from": "DISABLE_ONLINE", + "to": "DISABLE_MAINTENANCE", + "trigger": "to_maintenance" + }, + { + "from": "DISABLE_ONLINE", + "to": "DISABLE_ONLINE", + "trigger": "to_online" + }, + { + "from": "DISABLE_ONLINE", + "to": "DISABLE_ONLINE", + "trigger": "disable_succeeded" + }, + { + "from": "DISABLE_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "disable_failed" + }, + { + "from": "DISABLE_ONLINE", + "to": "STANDBY_ONLINE", + "trigger": "standby_succeeded" + }, + { + "from": "DISABLE_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "standby_failed" + }, + { + "from": "DISABLE_ONLINE", + "to": "OFF_ONLINE", + "trigger": "off_succeeded" + }, + { + "from": "DISABLE_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "off_failed" + }, + { + "from": "DISABLE_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "fatal_error" + }, + { + "from": "DISABLE_OFFLINE", + "to": "DISABLE_OFFLINE", + "trigger": "to_offline" + }, + { + "from": "DISABLE_OFFLINE", + "to": "DISABLE_MAINTENANCE", + "trigger": "to_maintenance" + }, + { + "from": "DISABLE_OFFLINE", + "to": "DISABLE_ONLINE", + "trigger": "to_online" + }, + { + "from": "DISABLE_OFFLINE", + "to": "DISABLE_NOTFITTED", + "trigger": "to_notfitted" + }, + { + "from": "DISABLE_OFFLINE", + "to": "DISABLE_RESERVED", + "trigger": "to_reserved" + }, + { + "from": "DISABLE_OFFLINE", + "to": "DISABLE_OFFLINE", + "trigger": "disable_succeeded" + }, + { + "from": "DISABLE_OFFLINE", + "to": "FAULT_OFFLINE", + "trigger": "disable_failed" + }, + { + "from": "DISABLE_OFFLINE", + "to": "FAULT_OFFLINE", + "trigger": "fatal_error" + }, + { + "from": "DISABLE_NOTFITTED", + "to": "DISABLE_NOTFITTED", + "trigger": "to_notfitted" + }, + { + "from": "DISABLE_NOTFITTED", + "to": "DISABLE_OFFLINE", + "trigger": "to_offline" + }, + { + "from": "DISABLE_NOTFITTED", + "to": "DISABLE_RESERVED", + "trigger": "to_reserved" + }, + { + "from": "DISABLE_NOTFITTED", + "to": "DISABLE_NOTFITTED", + "trigger": "disable_succeeded" + }, + { + "from": "DISABLE_NOTFITTED", + "to": "FAULT_NOTFITTED", + "trigger": "disable_failed" + }, + { + "from": "DISABLE_NOTFITTED", + "to": "FAULT_NOTFITTED", + "trigger": "fatal_error" + }, + { + "from": "DISABLE_RESERVED", + "to": "DISABLE_RESERVED", + "trigger": "to_reserved" + }, + { + "from": "DISABLE_RESERVED", + "to": "DISABLE_NOTFITTED", + "trigger": "to_notfitted" + }, + { + "from": "DISABLE_RESERVED", + "to": "DISABLE_OFFLINE", + "trigger": "to_offline" + }, + { + "from": "DISABLE_RESERVED", + "to": "DISABLE_RESERVED", + "trigger": "disable_succeeded" + }, + { + "from": "DISABLE_RESERVED", + "to": "FAULT_RESERVED", + "trigger": "disable_failed" + }, + { + "from": "DISABLE_RESERVED", + "to": "FAULT_RESERVED", + "trigger": "fatal_error" + }, + { + "from": "STANDBY_MAINTENANCE", + "to": "DISABLE_MAINTENANCE", + "trigger": "disable_succeeded" + }, + { + "from": "STANDBY_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "disable_failed" + }, + { + "from": "STANDBY_MAINTENANCE", + "to": "STANDBY_MAINTENANCE", + "trigger": "standby_succeeded" + }, + { + "from": "STANDBY_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "standby_failed" + }, + { + "from": "STANDBY_MAINTENANCE", + "to": "OFF_MAINTENANCE", + "trigger": "off_succeeded" + }, + { + "from": "STANDBY_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "off_failed" + }, + { + "from": "STANDBY_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "fatal_error" + }, + { + "from": "STANDBY_ONLINE", + "to": "DISABLE_ONLINE", + "trigger": "disable_succeeded" + }, + { + "from": "STANDBY_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "disable_failed" + }, + { + "from": "STANDBY_ONLINE", + "to": "STANDBY_ONLINE", + "trigger": "standby_succeeded" + }, + { + "from": "STANDBY_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "standby_failed" + }, + { + "from": "STANDBY_ONLINE", + "to": "OFF_ONLINE", + "trigger": "off_succeeded" + }, + { + "from": "STANDBY_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "off_failed" + }, + { + "from": "STANDBY_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "fatal_error" + }, + { + "from": "OFF_MAINTENANCE", + "to": "DISABLE_MAINTENANCE", + "trigger": "disable_succeeded" + }, + { + "from": "OFF_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "disable_failed" + }, + { + "from": "OFF_MAINTENANCE", + "to": "STANDBY_MAINTENANCE", + "trigger": "standby_succeeded" + }, + { + "from": "OFF_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "standby_failed" + }, + { + "from": "OFF_MAINTENANCE", + "to": "OFF_MAINTENANCE", + "trigger": "off_succeeded" + }, + { + "from": "OFF_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "off_failed" + }, + { + "from": "OFF_MAINTENANCE", + "to": "EMPTY_MAINTENANCE", + "trigger": "on_succeeded" + }, + { + "from": "OFF_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "on_failed" + }, + { + "from": "OFF_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "fatal_error" + }, + { + "from": "OFF_ONLINE", + "to": "DISABLE_ONLINE", + "trigger": "disable_succeeded" + }, + { + "from": "OFF_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "disable_failed" + }, + { + "from": "OFF_ONLINE", + "to": "STANDBY_ONLINE", + "trigger": "standby_succeeded" + }, + { + "from": "OFF_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "standby_failed" + }, + { + "from": "OFF_ONLINE", + "to": "OFF_ONLINE", + "trigger": "off_succeeded" + }, + { + "from": "OFF_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "off_failed" + }, + { + "from": "OFF_ONLINE", + "to": "EMPTY_ONLINE", + "trigger": "on_succeeded" + }, + { + "from": "OFF_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "on_failed" + }, + { + "from": "OFF_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "fatal_error" + }, + { + "from": "EMPTY_MAINTENANCE", + "to": "OFF_MAINTENANCE", + "trigger": "off_succeeded" + }, + { + "from": "EMPTY_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "off_failed" + }, + { + "from": "EMPTY_MAINTENANCE", + "to": "EMPTY_MAINTENANCE", + "trigger": "on_succeeded" + }, + { + "from": "EMPTY_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "on_failed" + }, + { + "from": "EMPTY_MAINTENANCE", + "to": "OBSFAULT_MAINTENANCE", + "trigger": "fatal_error" + }, + { + "from": "EMPTY_ONLINE", + "to": "OFF_ONLINE", + "trigger": "off_succeeded" + }, + { + "from": "EMPTY_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "off_failed" + }, + { + "from": "EMPTY_ONLINE", + "to": "EMPTY_ONLINE", + "trigger": "on_succeeded" + }, + { + "from": "EMPTY_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "on_failed" + }, + { + "from": "EMPTY_ONLINE", + "to": "OBSFAULT_ONLINE", + "trigger": "fatal_error" + }, + { + "from": "EMPTY_ONLINE", + "to": "RESOURCING_ONLINE", + "trigger": "assign_started" + }, + { + "from": "EMPTY_MAINTENANCE", + "to": "RESOURCING_MAINTENANCE", + "trigger": "assign_started" + }, + { + "from": "RESOURCING_ONLINE", + "to": "OFF_ONLINE", + "trigger": "off_succeeded" + }, + { + "from": "RESOURCING_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "off_failed" + }, + { + "from": "RESOURCING_ONLINE", + "to": "RESOURCING_ONLINE", + "trigger": "on_succeeded" + }, + { + "from": "RESOURCING_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "on_failed" + }, + { + "from": "RESOURCING_ONLINE", + "to": "IDLE_ONLINE", + "trigger": "resourcing_succeeded_some_resources" + }, + { + "from": "RESOURCING_ONLINE", + "to": "EMPTY_ONLINE", + "trigger": "resourcing_succeeded_no_resources" + }, + { + "from": "RESOURCING_ONLINE", + "to": "OBSFAULT_ONLINE", + "trigger": "resourcing_failed" + }, + { + "from": "RESOURCING_ONLINE", + "to": "OBSFAULT_ONLINE", + "trigger": "fatal_error" + }, + { + "from": "RESOURCING_MAINTENANCE", + "to": "OFF_MAINTENANCE", + "trigger": "off_succeeded" + }, + { + "from": "RESOURCING_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "off_failed" + }, + { + "from": "RESOURCING_MAINTENANCE", + "to": "RESOURCING_MAINTENANCE", + "trigger": "on_succeeded" + }, + { + "from": "RESOURCING_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "on_failed" + }, + { + "from": "RESOURCING_MAINTENANCE", + "to": "IDLE_MAINTENANCE", + "trigger": "resourcing_succeeded_some_resources" + }, + { + "from": "RESOURCING_MAINTENANCE", + "to": "EMPTY_MAINTENANCE", + "trigger": "resourcing_succeeded_no_resources" + }, + { + "from": "RESOURCING_MAINTENANCE", + "to": "OBSFAULT_MAINTENANCE", + "trigger": "resourcing_failed" + }, + { + "from": "RESOURCING_MAINTENANCE", + "to": "OBSFAULT_MAINTENANCE", + "trigger": "fatal_error" + }, + { + "from": "IDLE_ONLINE", + "to": "OFF_ONLINE", + "trigger": "off_succeeded" + }, + { + "from": "IDLE_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "off_failed" + }, + { + "from": "IDLE_ONLINE", + "to": "IDLE_ONLINE", + "trigger": "on_succeeded" + }, + { + "from": "IDLE_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "on_failed" + }, + { + "from": "IDLE_ONLINE", + "to": "RESOURCING_ONLINE", + "trigger": "assign_started" + }, + { + "from": "IDLE_ONLINE", + "to": "RESOURCING_ONLINE", + "trigger": "release_started" + }, + { + "from": "IDLE_ONLINE", + "to": "CONFIGURING_ONLINE", + "trigger": "configure_started" + }, + { + "from": "IDLE_ONLINE", + "to": "ABORTING_ONLINE", + "trigger": "abort_started" + }, + { + "from": "IDLE_ONLINE", + "to": "OBSFAULT_ONLINE", + "trigger": "fatal_error" + }, + { + "from": "IDLE_MAINTENANCE", + "to": "OFF_MAINTENANCE", + "trigger": "off_succeeded" + }, + { + "from": "IDLE_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "off_failed" + }, + { + "from": "IDLE_MAINTENANCE", + "to": "IDLE_MAINTENANCE", + "trigger": "on_succeeded" + }, + { + "from": "IDLE_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "on_failed" + }, + { + "from": "IDLE_MAINTENANCE", + "to": "RESOURCING_MAINTENANCE", + "trigger": "assign_started" + }, + { + "from": "IDLE_MAINTENANCE", + "to": "RESOURCING_MAINTENANCE", + "trigger": "release_started" + }, + { + "from": "IDLE_MAINTENANCE", + "to": "CONFIGURING_MAINTENANCE", + "trigger": "configure_started" + }, + { + "from": "IDLE_MAINTENANCE", + "to": "ABORTING_MAINTENANCE", + "trigger": "abort_started" + }, + { + "from": "IDLE_MAINTENANCE", + "to": "OBSFAULT_MAINTENANCE", + "trigger": "fatal_error" + }, + { + "from": "CONFIGURING_ONLINE", + "to": "OFF_ONLINE", + "trigger": "off_succeeded" + }, + { + "from": "CONFIGURING_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "off_failed" + }, + { + "from": "CONFIGURING_ONLINE", + "to": "CONFIGURING_ONLINE", + "trigger": "on_succeeded" + }, + { + "from": "CONFIGURING_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "on_failed" + }, + { + "from": "CONFIGURING_ONLINE", + "to": "READY_ONLINE", + "trigger": "configure_succeeded" + }, + { + "from": "CONFIGURING_ONLINE", + "to": "OBSFAULT_ONLINE", + "trigger": "configure_failed" + }, + { + "from": "CONFIGURING_ONLINE", + "to": "ABORTING_ONLINE", + "trigger": "abort_started" + }, + { + "from": "CONFIGURING_ONLINE", + "to": "OBSFAULT_ONLINE", + "trigger": "fatal_error" + }, + { + "from": "CONFIGURING_MAINTENANCE", + "to": "OFF_MAINTENANCE", + "trigger": "off_succeeded" + }, + { + "from": "CONFIGURING_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "off_failed" + }, + { + "from": "CONFIGURING_MAINTENANCE", + "to": "CONFIGURING_MAINTENANCE", + "trigger": "on_succeeded" + }, + { + "from": "CONFIGURING_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "on_failed" + }, + { + "from": "CONFIGURING_MAINTENANCE", + "to": "READY_MAINTENANCE", + "trigger": "configure_succeeded" + }, + { + "from": "CONFIGURING_MAINTENANCE", + "to": "OBSFAULT_MAINTENANCE", + "trigger": "configure_failed" + }, + { + "from": "CONFIGURING_MAINTENANCE", + "to": "ABORTING_MAINTENANCE", + "trigger": "abort_started" + }, + { + "from": "CONFIGURING_MAINTENANCE", + "to": "OBSFAULT_MAINTENANCE", + "trigger": "fatal_error" + }, + { + "from": "READY_ONLINE", + "to": "OFF_ONLINE", + "trigger": "off_succeeded" + }, + { + "from": "READY_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "off_failed" + }, + { + "from": "READY_ONLINE", + "to": "READY_ONLINE", + "trigger": "on_succeeded" + }, + { + "from": "READY_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "on_failed" + }, + { + "from": "READY_ONLINE", + "to": "IDLE_ONLINE", + "trigger": "end_succeeded" + }, + { + "from": "READY_ONLINE", + "to": "OBSFAULT_ONLINE", + "trigger": "end_failed" + }, + { + "from": "READY_ONLINE", + "to": "CONFIGURING_ONLINE", + "trigger": "configure_started" + }, + { + "from": "READY_ONLINE", + "to": "ABORTING_ONLINE", + "trigger": "abort_started" + }, + { + "from": "READY_ONLINE", + "to": "SCANNING_ONLINE", + "trigger": "scan_started" + }, + { + "from": "READY_ONLINE", + "to": "OBSFAULT_ONLINE", + "trigger": "fatal_error" + }, + { + "from": "READY_MAINTENANCE", + "to": "OFF_MAINTENANCE", + "trigger": "off_succeeded" + }, + { + "from": "READY_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "off_failed" + }, + { + "from": "READY_MAINTENANCE", + "to": "READY_MAINTENANCE", + "trigger": "on_succeeded" + }, + { + "from": "READY_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "on_failed" + }, + { + "from": "READY_MAINTENANCE", + "to": "IDLE_MAINTENANCE", + "trigger": "end_succeeded" + }, + { + "from": "READY_MAINTENANCE", + "to": "OBSFAULT_MAINTENANCE", + "trigger": "end_failed" + }, + { + "from": "READY_MAINTENANCE", + "to": "CONFIGURING_MAINTENANCE", + "trigger": "configure_started" + }, + { + "from": "READY_MAINTENANCE", + "to": "ABORTING_MAINTENANCE", + "trigger": "abort_started" + }, + { + "from": "READY_MAINTENANCE", + "to": "SCANNING_MAINTENANCE", + "trigger": "scan_started" + }, + { + "from": "READY_MAINTENANCE", + "to": "OBSFAULT_MAINTENANCE", + "trigger": "fatal_error" + }, + { + "from": "SCANNING_ONLINE", + "to": "OFF_ONLINE", + "trigger": "off_succeeded" + }, + { + "from": "SCANNING_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "off_failed" + }, + { + "from": "SCANNING_ONLINE", + "to": "SCANNING_ONLINE", + "trigger": "on_succeeded" + }, + { + "from": "SCANNING_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "on_failed" + }, + { + "from": "SCANNING_ONLINE", + "to": "READY_ONLINE", + "trigger": "scan_succeeded" + }, + { + "from": "SCANNING_ONLINE", + "to": "OBSFAULT_ONLINE", + "trigger": "scan_failed" + }, + { + "from": "SCANNING_ONLINE", + "to": "READY_ONLINE", + "trigger": "end_scan_succeeded" + }, + { + "from": "SCANNING_ONLINE", + "to": "OBSFAULT_ONLINE", + "trigger": "end_scan_failed" + }, + { + "from": "SCANNING_ONLINE", + "to": "ABORTING_ONLINE", + "trigger": "abort_started" + }, + { + "from": "SCANNING_ONLINE", + "to": "OBSFAULT_ONLINE", + "trigger": "fatal_error" + }, + { + "from": "SCANNING_MAINTENANCE", + "to": "OFF_MAINTENANCE", + "trigger": "off_succeeded" + }, + { + "from": "SCANNING_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "off_failed" + }, + { + "from": "SCANNING_MAINTENANCE", + "to": "SCANNING_MAINTENANCE", + "trigger": "on_succeeded" + }, + { + "from": "SCANNING_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "on_failed" + }, + { + "from": "SCANNING_MAINTENANCE", + "to": "READY_MAINTENANCE", + "trigger": "scan_succeeded" + }, + { + "from": "SCANNING_MAINTENANCE", + "to": "OBSFAULT_MAINTENANCE", + "trigger": "scan_failed" + }, + { + "from": "SCANNING_MAINTENANCE", + "to": "READY_MAINTENANCE", + "trigger": "end_scan_succeeded" + }, + { + "from": "SCANNING_MAINTENANCE", + "to": "OBSFAULT_MAINTENANCE", + "trigger": "end_scan_failed" + }, + { + "from": "SCANNING_MAINTENANCE", + "to": "ABORTING_MAINTENANCE", + "trigger": "abort_started" + }, + { + "from": "SCANNING_MAINTENANCE", + "to": "OBSFAULT_MAINTENANCE", + "trigger": "fatal_error" + }, + { + "from": "ABORTING_ONLINE", + "to": "OFF_ONLINE", + "trigger": "off_succeeded" + }, + { + "from": "ABORTING_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "off_failed" + }, + { + "from": "ABORTING_ONLINE", + "to": "ABORTING_ONLINE", + "trigger": "on_succeeded" + }, + { + "from": "ABORTING_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "on_failed" + }, + { + "from": "ABORTING_ONLINE", + "to": "ABORTED_ONLINE", + "trigger": "abort_succeeded" + }, + { + "from": "ABORTING_ONLINE", + "to": "OBSFAULT_ONLINE", + "trigger": "abort_failed" + }, + { + "from": "ABORTING_ONLINE", + "to": "OBSFAULT_ONLINE", + "trigger": "fatal_error" + }, + { + "from": "ABORTING_MAINTENANCE", + "to": "OFF_MAINTENANCE", + "trigger": "off_succeeded" + }, + { + "from": "ABORTING_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "off_failed" + }, + { + "from": "ABORTING_MAINTENANCE", + "to": "ABORTING_MAINTENANCE", + "trigger": "on_succeeded" + }, + { + "from": "ABORTING_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "on_failed" + }, + { + "from": "ABORTING_MAINTENANCE", + "to": "ABORTED_MAINTENANCE", + "trigger": "abort_succeeded" + }, + { + "from": "ABORTING_MAINTENANCE", + "to": "OBSFAULT_MAINTENANCE", + "trigger": "abort_failed" + }, + { + "from": "ABORTING_MAINTENANCE", + "to": "OBSFAULT_MAINTENANCE", + "trigger": "fatal_error" + }, + { + "from": "ABORTED_ONLINE", + "to": "OFF_ONLINE", + "trigger": "off_succeeded" + }, + { + "from": "ABORTED_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "off_failed" + }, + { + "from": "ABORTED_ONLINE", + "to": "ABORTED_ONLINE", + "trigger": "on_succeeded" + }, + { + "from": "ABORTED_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "on_failed" + }, + { + "from": "ABORTED_ONLINE", + "to": "RESETTING_ONLINE", + "trigger": "obs_reset_started" + }, + { + "from": "ABORTED_ONLINE", + "to": "RESTARTING_ONLINE", + "trigger": "restart_started" + }, + { + "from": "ABORTED_ONLINE", + "to": "OBSFAULT_ONLINE", + "trigger": "fatal_error" + }, + { + "from": "ABORTED_MAINTENANCE", + "to": "OFF_MAINTENANCE", + "trigger": "off_succeeded" + }, + { + "from": "ABORTED_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "off_failed" + }, + { + "from": "ABORTED_MAINTENANCE", + "to": "ABORTED_MAINTENANCE", + "trigger": "on_succeeded" + }, + { + "from": "ABORTED_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "on_failed" + }, + { + "from": "ABORTED_MAINTENANCE", + "to": "RESETTING_MAINTENANCE", + "trigger": "obs_reset_started" + }, + { + "from": "ABORTED_MAINTENANCE", + "to": "RESTARTING_MAINTENANCE", + "trigger": "restart_started" + }, + { + "from": "ABORTED_MAINTENANCE", + "to": "OBSFAULT_MAINTENANCE", + "trigger": "fatal_error" + }, + { + "from": "OBSFAULT_ONLINE", + "to": "OFF_ONLINE", + "trigger": "off_succeeded" + }, + { + "from": "OBSFAULT_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "off_failed" + }, + { + "from": "OBSFAULT_ONLINE", + "to": "OBSFAULT_ONLINE", + "trigger": "on_succeeded" + }, + { + "from": "OBSFAULT_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "on_failed" + }, + { + "from": "OBSFAULT_ONLINE", + "to": "RESETTING_ONLINE", + "trigger": "obs_reset_started" + }, + { + "from": "OBSFAULT_ONLINE", + "to": "RESTARTING_ONLINE", + "trigger": "restart_started" + }, + { + "from": "OBSFAULT_ONLINE", + "to": "OBSFAULT_ONLINE", + "trigger": "fatal_error" + }, + { + "from": "OBSFAULT_MAINTENANCE", + "to": "OFF_MAINTENANCE", + "trigger": "off_succeeded" + }, + { + "from": "OBSFAULT_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "off_failed" + }, + { + "from": "OBSFAULT_MAINTENANCE", + "to": "OBSFAULT_MAINTENANCE", + "trigger": "on_succeeded" + }, + { + "from": "OBSFAULT_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "on_failed" + }, + { + "from": "OBSFAULT_MAINTENANCE", + "to": "RESETTING_MAINTENANCE", + "trigger": "obs_reset_started" + }, + { + "from": "OBSFAULT_MAINTENANCE", + "to": "RESTARTING_MAINTENANCE", + "trigger": "restart_started" + }, + { + "from": "OBSFAULT_MAINTENANCE", + "to": "OBSFAULT_MAINTENANCE", + "trigger": "fatal_error" + }, + { + "from": "RESETTING_ONLINE", + "to": "OFF_ONLINE", + "trigger": "off_succeeded" + }, + { + "from": "RESETTING_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "off_failed" + }, + { + "from": "RESETTING_ONLINE", + "to": "RESETTING_ONLINE", + "trigger": "on_succeeded" + }, + { + "from": "RESETTING_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "on_failed" + }, + { + "from": "RESETTING_ONLINE", + "to": "IDLE_ONLINE", + "trigger": "obs_reset_succeeded" + }, + { + "from": "RESETTING_ONLINE", + "to": "OBSFAULT_ONLINE", + "trigger": "obs_reset_failed" + }, + { + "from": "RESETTING_ONLINE", + "to": "ABORTING_ONLINE", + "trigger": "abort_started" + }, + { + "from": "RESETTING_ONLINE", + "to": "OBSFAULT_ONLINE", + "trigger": "fatal_error" + }, + { + "from": "RESETTING_MAINTENANCE", + "to": "OFF_MAINTENANCE", + "trigger": "off_succeeded" + }, + { + "from": "RESETTING_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "off_failed" + }, + { + "from": "RESETTING_MAINTENANCE", + "to": "RESETTING_MAINTENANCE", + "trigger": "on_succeeded" + }, + { + "from": "RESETTING_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "on_failed" + }, + { + "from": "RESETTING_MAINTENANCE", + "to": "IDLE_MAINTENANCE", + "trigger": "obs_reset_succeeded" + }, + { + "from": "RESETTING_MAINTENANCE", + "to": "OBSFAULT_MAINTENANCE", + "trigger": "obs_reset_failed" + }, + { + "from": "RESETTING_MAINTENANCE", + "to": "ABORTING_MAINTENANCE", + "trigger": "abort_started" + }, + { + "from": "RESETTING_MAINTENANCE", + "to": "OBSFAULT_MAINTENANCE", + "trigger": "fatal_error" + }, + { + "from": "RESTARTING_ONLINE", + "to": "OFF_ONLINE", + "trigger": "off_succeeded" + }, + { + "from": "RESTARTING_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "off_failed" + }, + { + "from": "RESTARTING_ONLINE", + "to": "RESTARTING_ONLINE", + "trigger": "on_succeeded" + }, + { + "from": "RESTARTING_ONLINE", + "to": "FAULT_ONLINE", + "trigger": "on_failed" + }, + { + "from": "RESTARTING_ONLINE", + "to": "EMPTY_ONLINE", + "trigger": "restart_succeeded" + }, + { + "from": "RESTARTING_ONLINE", + "to": "OBSFAULT_ONLINE", + "trigger": "restart_failed" + }, + { + "from": "RESTARTING_ONLINE", + "to": "OBSFAULT_ONLINE", + "trigger": "fatal_error" + }, + { + "from": "RESTARTING_MAINTENANCE", + "to": "OFF_MAINTENANCE", + "trigger": "off_succeeded" + }, + { + "from": "RESTARTING_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "off_failed" + }, + { + "from": "RESTARTING_MAINTENANCE", + "to": "RESTARTING_MAINTENANCE", + "trigger": "on_succeeded" + }, + { + "from": "RESTARTING_MAINTENANCE", + "to": "FAULT_MAINTENANCE", + "trigger": "on_failed" + }, + { + "from": "RESTARTING_MAINTENANCE", + "to": "EMPTY_MAINTENANCE", + "trigger": "restart_succeeded" + }, + { + "from": "RESTARTING_MAINTENANCE", + "to": "OBSFAULT_MAINTENANCE", + "trigger": "restart_failed" + }, + { + "from": "RESTARTING_MAINTENANCE", + "to": "OBSFAULT_MAINTENANCE", + "trigger": "fatal_error" + } ] -] \ No newline at end of file +} \ No newline at end of file diff --git a/tests/test_base_device.py b/tests/test_base_device.py index be517b0e744fcbbd5beb528e24f82587dd533386..049b61a60d4d5b69b9d27da1e807db9e6f23e4c4 100644 --- a/tests/test_base_device.py +++ b/tests/test_base_device.py @@ -27,12 +27,12 @@ from ska.base.base_device import ( _PYTHON_TO_TANGO_LOGGING_LEVEL, LoggingUtils, LoggingTargetError, - SKABaseDeviceStateModel, + DeviceStateModel, TangoLoggingServiceHandler, ) from ska.base.faults import StateModelError -from .conftest import load_data, StateMachineTester +from .conftest import load_state_machine_spec, StateMachineTester # PROTECTED REGION END # // SKABaseDevice.test_additional_imports # Device test case @@ -326,42 +326,28 @@ class TestLoggingUtils: @pytest.fixture def device_state_model(): """ - Yields a new SKABaseDeviceStateModel for testing + Yields a new DeviceStateModel for testing + + :yields: a DeviceStateModel instance to be tested """ - yield SKABaseDeviceStateModel(logging.getLogger()) + yield DeviceStateModel(logging.getLogger()) -@pytest.mark.state_machine_tester(load_data("base_device_state_machine")) -class TestSKABaseDeviceStateModel(StateMachineTester): +@pytest.mark.state_machine_tester(load_state_machine_spec("device_state_machine")) +class TestDeviceStateModel(StateMachineTester): """ This class contains the test suite for the ska.base.SKABaseDevice class. """ + @pytest.fixture def machine(self, device_state_model): """ Fixture that returns the state machine under test in this class + + :yields: the state machine under test """ yield device_state_model - state_checks = { - "UNINITIALISED": - (None, None), - "FAULT_ENABLED": - ([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.FAULT), - "FAULT_DISABLED": - ([AdminMode.NOT_FITTED, AdminMode.OFFLINE], DevState.FAULT), - "INIT_ENABLED": - ([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.INIT), - "INIT_DISABLED": - ([AdminMode.NOT_FITTED, AdminMode.OFFLINE], DevState.INIT), - "DISABLED": - ([AdminMode.NOT_FITTED, AdminMode.OFFLINE], DevState.DISABLE), - "OFF": - ([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.OFF), - "ON": - ([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.ON), - } - def assert_state(self, machine, state): """ Assert the current state of this state machine, based on the @@ -372,29 +358,35 @@ class TestSKABaseDeviceStateModel(StateMachineTester): :type machine: state machine object instance :param state: the state that we are asserting to be the current state of the state machine under test - :type state: str + :type state: dict + """ + assert machine.admin_mode == state["admin_mode"] + assert machine.op_state == state["op_state"] + + def is_action_allowed(self, machine, action): """ + Returns whether the state machine under test thinks an action + is permitted in its current state - (admin_modes, op_state) = self.state_checks[state] - if admin_modes is None: - assert machine.admin_mode is None - else: - assert machine.admin_mode in admin_modes - if op_state is None: - assert machine.op_state is None - else: - assert machine.op_state == op_state + :param machine: the state machine under test + :type machine: state machine object instance + :param action: action to be performed on the state machine + :type action: str + """ + return machine.is_action_allowed(action) def perform_action(self, machine, action): """ Perform a given action on the state machine under test. + :param machine: the state machine under test + :type machine: state machine object instance :param action: action to be performed on the state machine :type action: str """ machine.perform_action(action) - def check_action_disallowed(self, machine, action): + def check_action_fails(self, machine, action): """ Assert that performing a given action on the state maching under test fails in its current state. @@ -415,9 +407,9 @@ class TestSKABaseDeviceStateModel(StateMachineTester): :type machine: state machine object instance :param target_state: the state that we want to get the state machine under test into - :type target_state: str + :type target_state: dict """ - machine._straight_to_state(target_state) + machine._straight_to_state(**target_state) # PROTECTED REGION END # // SKABaseDevice.test_SKABaseDevice_decorators @@ -509,45 +501,51 @@ class TestSKABaseDevice(object): state_callback.assert_call(DevState.ON) status_callback.assert_call("The device is in ON state.") - # Check that we can't turn it on when it is already on - with pytest.raises(DevFailed): - tango_context.device.On() + # Check that we can turn it on when it is already on + tango_context.device.On() state_callback.assert_not_called() status_callback.assert_not_called() - # Now turn it off and check that we can turn it on again. - tango_context.device.Off() - state_callback.assert_call(DevState.OFF) - status_callback.assert_call("The device is in OFF state.") + def test_Disable(self, tango_context): + """ + Test for Disable command + """ + assert tango_context.device.state() == DevState.OFF - tango_context.device.On() - state_callback.assert_call(DevState.ON) - status_callback.assert_call("The device is in ON state.") + # Check that we can disable it + tango_context.device.Disable() + assert tango_context.device.state() == DevState.DISABLE + + # Check that we can disable it when it is already disabled + tango_context.device.Disable() + + def test_Standby(self, tango_context): + """ + Test for Standby command + """ + assert tango_context.device.state() == DevState.OFF + + # Check that we can put it on standby + tango_context.device.Standby() + assert tango_context.device.state() == DevState.STANDBY + + # Check that we can put it on standby when it is already on standby + tango_context.device.Standby() def test_Off(self, tango_context, tango_change_event_helper): """ - Test for On command + Test for Off command """ state_callback = tango_change_event_helper.subscribe("state") status_callback = tango_change_event_helper.subscribe("status") state_callback.assert_call(DevState.OFF) status_callback.assert_call("The device is in OFF state.") - # Check that we can't turn off a device that isn't on - with pytest.raises(DevFailed): - tango_context.device.Off() + # Check that we can turn off a device that is already off + tango_context.device.Off() state_callback.assert_not_called() status_callback.assert_not_called() - # Now turn it on and check that we can turn it off - tango_context.device.On() - state_callback.assert_call(DevState.ON) - status_callback.assert_call("The device is in ON state.") - - tango_context.device.Off() - state_callback.assert_call(DevState.OFF) - status_callback.assert_call("The device is in OFF state.") - # PROTECTED REGION ID(SKABaseDevice.test_buildState_decorators) ENABLED START # # PROTECTED REGION END # // SKABaseDevice.test_buildState_decorators def test_buildState(self, tango_context): @@ -651,15 +649,24 @@ class TestSKABaseDevice(object): def test_adminMode(self, tango_context, tango_change_event_helper): """Test for adminMode""" # PROTECTED REGION ID(SKABaseDevice.test_adminMode) ENABLED START # + tango_context.device.Disable() assert tango_context.device.adminMode == AdminMode.MAINTENANCE admin_mode_callback = tango_change_event_helper.subscribe("adminMode") admin_mode_callback.assert_call(AdminMode.MAINTENANCE) + tango_context.device.adminMode = AdminMode.OFFLINE + assert tango_context.device.adminMode == AdminMode.OFFLINE + admin_mode_callback.assert_call(AdminMode.OFFLINE) + tango_context.device.adminMode = AdminMode.ONLINE assert tango_context.device.adminMode == AdminMode.ONLINE admin_mode_callback.assert_call(AdminMode.ONLINE) + tango_context.device.adminMode = AdminMode.ONLINE + assert tango_context.device.adminMode == AdminMode.ONLINE + admin_mode_callback.assert_not_called() + # PROTECTED REGION END # // SKABaseDevice.test_adminMode # PROTECTED REGION ID(SKABaseDevice.test_controlMode_decorators) ENABLED START # diff --git a/tests/test_state_machines.py b/tests/test_state_machines.py index d44b641b982f856ed419691b5c16c4ee9ba28a94..dfa9208f45d7a04b4a04e69fc289324ff664d927 100644 --- a/tests/test_state_machines.py +++ b/tests/test_state_machines.py @@ -3,31 +3,57 @@ This module contains the tests for the ska.base.state_machine module. """ import pytest -from ska.base.state_machine import BaseDeviceStateMachine, ObservationStateMachine -from .conftest import load_data, TransitionsStateMachineTester +from ska.base.state_machine import ( + AdminModeStateMachine, + OperationStateMachine, + ObservationStateMachine, +) +from .conftest import load_state_machine_spec, TransitionsStateMachineTester -@pytest.mark.state_machine_tester(load_data("base_device_state_machine")) -class BaseDeviceStateMachineTester(TransitionsStateMachineTester): +@pytest.mark.state_machine_tester(load_state_machine_spec("operation_state_machine")) +class TestOperationStateMachine(TransitionsStateMachineTester): """ - This class contains the test for the BaseDeviceStateMachine class. + This class contains the test for the DeviceStateMachine class. """ + @pytest.fixture def machine(self): """ Fixture that returns the state machine under test in this class + + :yields: the state machine under test """ - yield BaseDeviceStateMachine() + yield OperationStateMachine() -@pytest.mark.state_machine_tester(load_data("observation_state_machine")) +@pytest.mark.state_machine_tester(load_state_machine_spec("admin_mode_state_machine")) +class TestAdminModeStateMachine(TransitionsStateMachineTester): + """ + This class contains the test for the DeviceStateMachine class. + """ + + @pytest.fixture + def machine(self): + """ + Fixture that returns the state machine under test in this class + + :yields: the state machine under test + """ + yield AdminModeStateMachine() + + +@pytest.mark.state_machine_tester(load_state_machine_spec("observation_state_machine")) class TestObservationStateMachine(TransitionsStateMachineTester): """ This class contains the test for the ObservationStateMachine class. """ + @pytest.fixture def machine(self): """ Fixture that returns the state machine under test in this class + + :yields: the state machine under test """ yield ObservationStateMachine() diff --git a/tests/test_subarray_device.py b/tests/test_subarray_device.py index 7f91bb27e9bd4db38f136af1ef1d3aa19cfdfa1d..2676f4aa3bf9244031b2c7a61c3355cd3843d078 100644 --- a/tests/test_subarray_device.py +++ b/tests/test_subarray_device.py @@ -18,11 +18,18 @@ from tango import DevState, DevFailed from ska.base import SKASubarray, SKASubarrayResourceManager, SKASubarrayStateModel from ska.base.commands import ResultCode from ska.base.control_model import ( - AdminMode, ControlMode, HealthState, ObsMode, ObsState, SimulationMode, TestMode + AdminMode, + ControlMode, + HealthState, + ObsMode, + ObsState, + SimulationMode, + TestMode, ) from ska.base.faults import CommandError, StateModelError -from .conftest import load_data, StateMachineTester +from .conftest import load_state_machine_spec, StateMachineTester + # PROTECTED REGION END # // SKASubarray.test_additional_imports @@ -34,11 +41,12 @@ def subarray_state_model(): yield SKASubarrayStateModel(logging.getLogger()) -@pytest.mark.state_machine_tester(load_data("subarray_state_machine")) +@pytest.mark.state_machine_tester(load_state_machine_spec("subarray_state_machine")) class TestSKASubarrayStateModel(StateMachineTester): """ This class contains the test for the SKASubarrayStateModel class. """ + @pytest.fixture def machine(self, subarray_state_model): """ @@ -46,46 +54,6 @@ class TestSKASubarrayStateModel(StateMachineTester): """ yield subarray_state_model - state_checks = { - "UNINITIALISED": - (None, None, ObsState.EMPTY), - "FAULT_ENABLED": - ([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.FAULT, ObsState.EMPTY), - "FAULT_DISABLED": - ([AdminMode.NOT_FITTED, AdminMode.OFFLINE], DevState.FAULT, ObsState.EMPTY), - "INIT_ENABLED": - ([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.INIT, ObsState.EMPTY), - "INIT_DISABLED": - ([AdminMode.NOT_FITTED, AdminMode.OFFLINE], DevState.INIT, ObsState.EMPTY), - "DISABLED": - ([AdminMode.NOT_FITTED, AdminMode.OFFLINE], DevState.DISABLE, ObsState.EMPTY), - "OFF": - ([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.OFF, ObsState.EMPTY), - "EMPTY": - ([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.ON, ObsState.EMPTY), - "RESOURCING": - ([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.ON, ObsState.RESOURCING), - "IDLE": - ([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.ON, ObsState.IDLE), - "CONFIGURING": - ([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.ON, ObsState.CONFIGURING), - "READY": - ([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.ON, ObsState.READY), - "SCANNING": - ([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.ON, ObsState.SCANNING), - "ABORTING": - ([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.ON, ObsState.ABORTING), - "ABORTED": - ([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.ON, ObsState.ABORTED), - "FAULT": - ([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.ON, ObsState.FAULT), - "RESETTING": - ([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.ON, ObsState.RESETTING), - "RESTARTING": - ([AdminMode.ONLINE, AdminMode.MAINTENANCE], DevState.ON, ObsState.RESTARTING), - - } - def assert_state(self, machine, state): """ Assert the current state of this state machine, based on the @@ -98,22 +66,21 @@ class TestSKASubarrayStateModel(StateMachineTester): state of the state machine under test :type state: str """ - # Debugging only -- machine is already tested - # assert self.model._state == state - # print(f"State is {state}") - (admin_modes, op_state, obs_state) = self.state_checks[state] - if admin_modes is None: - assert machine.admin_mode is None - else: - assert machine.admin_mode in admin_modes - if op_state is None: - assert machine.op_state is None - else: - assert machine.op_state == op_state - if obs_state is None: - assert machine.obs_state is None - else: - assert machine.obs_state == obs_state + assert machine.admin_mode == state["admin_mode"] + assert machine.op_state == state["op_state"] + assert machine.obs_state == state["obs_state"] + + def is_action_allowed(self, machine, action): + """ + Returns whether the state machine under state thinks an action + is permitted in its current state + + :param machine: the state machine under test + :type machine: state machine object instance + :param action: action to be performed on the state machine + :type action: str + """ + return machine.is_action_allowed(action) def perform_action(self, machine, action): """ @@ -126,9 +93,9 @@ class TestSKASubarrayStateModel(StateMachineTester): """ machine.perform_action(action) - def check_action_disallowed(self, machine, action): + def check_action_fails(self, machine, action): """ - Assert that performing a given action on the state maching under + Assert that attempting a given action on the state machine under test fails in its current state. :param machine: the state machine under test @@ -149,7 +116,7 @@ class TestSKASubarrayStateModel(StateMachineTester): machine under test into :type target_state: str """ - machine._straight_to_state(target_state) + machine._straight_to_state(**target_state) class TestSKASubarray: @@ -163,7 +130,7 @@ class TestSKASubarray: 'SkaLevel': '4', 'LoggingTargetsDefault': '', 'SubID': '', - } + } @classmethod def mocking(cls): @@ -418,18 +385,19 @@ class TestSKASubarray: """Test for adminMode""" # PROTECTED REGION ID(SKASubarray.test_adminMode) ENABLED START # assert tango_context.device.adminMode == AdminMode.MAINTENANCE - assert tango_context.device.state() == DevState.OFF + + tango_context.device.Disable() + assert tango_context.device.state() == DevState.DISABLE admin_mode_callback = tango_change_event_helper.subscribe("adminMode") dev_state_callback = tango_change_event_helper.subscribe("state") admin_mode_callback.assert_call(AdminMode.MAINTENANCE) - dev_state_callback.assert_call(DevState.OFF) + dev_state_callback.assert_call(DevState.DISABLE) tango_context.device.adminMode = AdminMode.OFFLINE assert tango_context.device.adminMode == AdminMode.OFFLINE assert tango_context.device.state() == DevState.DISABLE admin_mode_callback.assert_call(AdminMode.OFFLINE) - dev_state_callback.assert_call(DevState.DISABLE) # PROTECTED REGION END # // SKASubarray.test_adminMode @@ -538,6 +506,8 @@ class TestSKASubarray: def resource_manager(): """ Fixture that yields an SKASubarrayResourceManager + + :yields: a SKASubarrayResourceManager instance """ yield SKASubarrayResourceManager() @@ -546,6 +516,7 @@ class TestSKASubarrayResourceManager: """ Test suite for SKASubarrayResourceManager class """ + def test_ResourceManager_assign(self, resource_manager): """ Test that the ResourceManager assigns resource correctly. @@ -582,7 +553,6 @@ class TestSKASubarrayResourceManager: """ Test that the ResourceManager releases resource correctly. """ - resource_manager = SKASubarrayResourceManager() resource_manager.assign('{"example": ["A", "B", "C", "D"]}') # okay to release resources not assigned; does nothing @@ -625,40 +595,51 @@ class TestSKASubarray_commands: subarray_state_model ) - all_states = { - "UNINITIALISED", "FAULT_ENABLED", "FAULT_DISABLED", "INIT_ENABLED", - "INIT_DISABLED", "DISABLED", "OFF", "EMPTY", "RESOURCING", "IDLE", - "CONFIGURING", "READY", "SCANNING", "ABORTING", "ABORTED", "FAULT", - "RESETTING", "RESTARTING", - } - + machine_spec = load_state_machine_spec("subarray_state_machine") + states = machine_spec["states"] # in all states except EMPTY and IDLE, the assign resources command is # not permitted, should not be allowed, should fail, should have no # side-effect - for state in all_states - {"EMPTY", "IDLE"}: - subarray_state_model._straight_to_state(state) + for state in set(states) - { + "EMPTY_ONLINE", + "EMPTY_MAINTENANCE", + "IDLE_ONLINE", + "IDLE_MAINTENANCE", + }: + + subarray_state_model._straight_to_state(**states[state]) assert not assign_resources.is_allowed() with pytest.raises(CommandError): assign_resources('{"example": ["foo"]}') assert not len(resource_manager) assert resource_manager.get() == set() - assert subarray_state_model._state == state + assert subarray_state_model.admin_mode == states[state]["admin_mode"] + assert subarray_state_model.op_state == states[state]["op_state"] + assert subarray_state_model.obs_state == states[state]["obs_state"] # now push to empty, a state in which is IS allowed - subarray_state_model._straight_to_state("EMPTY") + subarray_state_model._straight_to_state(**states["EMPTY_ONLINE"]) assert assign_resources.is_allowed() assert assign_resources('{"example": ["foo"]}') == ( - ResultCode.OK, "AssignResources command completed OK" + ResultCode.OK, + "AssignResources command completed OK", ) assert len(resource_manager) == 1 assert resource_manager.get() == set(["foo"]) - assert subarray_state_model._state == "IDLE" + assert subarray_state_model.admin_mode == states["IDLE_ONLINE"]["admin_mode"] + assert subarray_state_model.op_state == states["IDLE_ONLINE"]["op_state"] + assert subarray_state_model.obs_state == states["IDLE_ONLINE"]["obs_state"] - # AssignResources is still allowed in ON_IDLE + # AssignResources is still allowed in IDLE assert assign_resources.is_allowed() assert assign_resources('{"example": ["bar"]}') == ( - ResultCode.OK, "AssignResources command completed OK" + ResultCode.OK, + "AssignResources command completed OK", ) assert len(resource_manager) == 2 assert resource_manager.get() == set(["foo", "bar"]) + + assert subarray_state_model.admin_mode == states["IDLE_ONLINE"]["admin_mode"] + assert subarray_state_model.op_state == states["IDLE_ONLINE"]["op_state"] + assert subarray_state_model.obs_state == states["IDLE_ONLINE"]["obs_state"]