Skip to content
Snippets Groups Projects
Commit 839e1b4c authored by Drew Devereux's avatar Drew Devereux
Browse files

Merge branch 'mccs-396' into 'master'

[SP-1501] State models, commands and component managers

See merge request ska-telescope/ska-tango-base!48
parents 7127b688 8db102c5
No related branches found
No related tags found
No related merge requests found
Showing
with 600 additions and 0 deletions
=======
Release
=======
.. automodule:: ska_tango_base.release
:members:
docs/source/api/subarray/_SubarrayObsStateMachine_autogenerated.png

372 KiB

==========================
Subarray Component Manager
==========================
.. automodule:: ska_tango_base.subarray.component_manager
:members:
========
Subarray
========
.. automodule:: ska_tango_base.subarray
.. toctree::
Subarray Obs State Model<subarray_obs_state_model>
Subarray Component Manager<component_manager>
Reference Subarray Component Manager<reference_component_manager>
Subarray Device<subarray_device>
====================================
Reference Subarray Component Manager
====================================
.. automodule:: ska_tango_base.subarray.reference_component_manager
:members:
===============
Subarray Device
===============
.. automodule:: ska_tango_base.subarray.subarray_device
:members:
EMPTY: The subarray has no resources
RESOURCING_EMPTY: The subarray has no resources;\nis performing a resourcing operation
RESOURCING_IDLE: The subarray has resources;\nis performing a resourcing operation
IDLE: The subarray is unconfigured
CONFIGURING_IDLE: The subarray is unconfigured;\nis performing a configuring operation
CONFIGURING_READY: The subarray is configured;\nis performing a configuring operation
READY: The subarray is configured
SCANNING: The subarray is scanning
ABORTING: The subarray is aborting
ABORTED: The subarray has aborted
RESETTING: The subarray is resetting to IDLE\nfrom FAULT or ABORTED state
RESTARTING: The subarray is restarting to EMPTY\nfrom FAULT or ABORTED state
FAULT: The subarray has faulted
EMPTY -down-> RESOURCING_EMPTY: assign_invoked
RESOURCING_EMPTY -up-> EMPTY: assign_completed
RESOURCING_EMPTY -up-> EMPTY: release_completed
RESOURCING_EMPTY -down-> RESOURCING_IDLE: component_resourced
RESOURCING_IDLE -up-> RESOURCING_EMPTY: component_unresourced
RESOURCING_IDLE -down-> IDLE: assign_completed
RESOURCING_IDLE -down-> IDLE: release_completed
IDLE -up-> RESOURCING_IDLE: assign_invoked
IDLE -up-> RESOURCING_IDLE: release_invoked
IDLE -down-> CONFIGURING_IDLE: configure_invoked
IDLE -left-> ABORTING: abort_invoked
CONFIGURING_IDLE -up-> IDLE: configure_completed
CONFIGURING_IDLE -down-> CONFIGURING_READY: component_configured
CONFIGURING_IDLE -left-> ABORTING: abort_invoked
CONFIGURING_READY -down-> READY: configure_completed
CONFIGURING_READY -left-> ABORTING: abort_invoked
READY -up-> CONFIGURING_READY: configure_invoked
READY -up-> IDLE: component_unconfigured
READY -down-> SCANNING: component_scanning
READY -left-> ABORTING: abort_invoked
SCANNING -up-> READY: component_not_scanning
SCANNING -left-> ABORTING: abort_invoked
ABORTING -up-> ABORTED: abort_completed
ABORTED -up-> RESETTING: obsreset_invoked
ABORTED -up-> RESTARTING: restart_invoked
RESETTING -down-> ABORTING: abort_invoked
RESETTING -right-> IDLE: obsreset_completed
RESTARTING -right-> EMPTY: restart_completed
[*] -up-> FAULT: component_obsfault\n(from any state)
FAULT -up-> RESETTING: obsreset_invoked
FAULT -up-> RESTARTING: restart_invoked
========================
Subarray Obs State Model
========================
.. automodule:: ska_tango_base.subarray.subarray_obs_state_model
:members:
EMPTY: The subarray has no resources
RESOURCING: The subarray is performing a resourcing operation
IDLE: The subarray is unconfigured
CONFIGURING: The subarray is performing a configuring operation
READY: The subarray is configured
SCANNING: The subarray is scanning
ABORTING: The subarray is aborting
ABORTED: The subarray has aborted
RESETTING: The subarray is resetting to IDLE\nfrom FAULT or ABORTED state
RESTARTING: The subarray is restarting to EMPTY\nfrom FAULT or ABORTED state
FAULT: The subarray has faulted
EMPTY -down-> RESOURCING: assign_invoked
RESOURCING -up-> EMPTY: assign_completed
RESOURCING -up-> EMPTY: release_completed
RESOURCING -down-> IDLE: assign_completed
RESOURCING -down-> IDLE: release_completed
IDLE -up-> RESOURCING: assign_invoked
IDLE -up-> RESOURCING: release_invoked
IDLE -down-> CONFIGURING: configure_invoked
IDLE -left-> ABORTING: abort_invoked
CONFIGURING -up-> IDLE: configure_completed
CONFIGURING -left-> ABORTING: abort_invoked
CONFIGURING -down-> READY: configure_completed
CONFIGURING -left-> ABORTING: abort_invoked
READY -up-> CONFIGURING: configure_invoked
READY -up-> IDLE: component_unconfigured
READY -down-> SCANNING: component_scanning
READY -left-> ABORTING: abort_invoked
SCANNING -up-> READY: component_not_scanning
SCANNING -left-> ABORTING: abort_invoked
ABORTING -up-> ABORTED: abort_completed
ABORTED -up-> RESETTING: obsreset_invoked
ABORTED -up-> RESTARTING: restart_invoked
RESETTING -down-> ABORTING: abort_invoked
RESETTING -right-> IDLE: obsreset_completed
RESTARTING -right-> EMPTY: restart_completed
[*] -up-> FAULT: component_obsfault\n(from any state)
FAULT -up-> RESETTING: obsreset_invoked
FAULT -up-> RESTARTING: restart_invoked
================
Tel State Device
================
.. automodule:: ska_tango_base.tel_state_device
:members:
=====
Utils
=====
.. automodule:: ska_tango_base.utils
:members:
......@@ -99,9 +99,13 @@ extensions = [
'sphinx.ext.doctest',
'sphinx.ext.todo',
'sphinx.ext.coverage',
"sphinx.ext.inheritance_diagram",
'sphinx.ext.viewcode',
'sphinxcontrib.plantuml'
]
autoclass_content = "class"
plantuml_syntax_error_image = True
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
......
=================================
Components and component managers
=================================
A fundamental assumption of this package is that each Tango device
exists to provide monitoring and control of some *component* of a SKA
telescope.
A *component* could be (for example):
* Hardware such as an antenna, dish, atomic clock, TPM, switch, etc
* An external service such as a database or cluster workload
manager
* A software process or thread launched by the Tango device.
* In a hierarchical system, a group of subservient Tango devices.
By analogy, if the *component* is a television, the Tango device would
be the remote control for that television.
Tango devices and their components
----------------------------------
Note the distinction between a component and the Tango device that is
responsible for monitoring and controlling that component.
A component might be hardware equipment installed on site, such as a
dish or an antenna. The Tango device that monitors that component is a
software object, in a process running on a server, probably located in a
server room some distance away. Thus the Tango device and its component
are largely independent of each other:
* A Tango device may be running normally when its component is in a
fault state, or turned off, or even not fitted. Device states like
``OFF`` and ``FAULT`` represent the state of the monitored component.
A Tango device that reports ``OFF`` state is running normally, and
reporting that its component is turned off. A Tango device that
reports ``FAULT`` state is running normally, and reporting that its
component is in a fault state.
* When a Tango device itself experiences a fault (for example, its
server crashes), this is not expected to affect the component. The
component continues to run; the only impact is it can no longer be
monitored or controlled.
By analogy: when the batteries in your TV remote control go flat, the
TV continues to run.
* We should not assume that a component's state is governed solely by
its Tango device. On the contrary, components are influenced by a
wide range of factors. For example, the following are ways in which a
component might be switched off:
* Its Tango device switches it off via its software interface;
* Some other software entity switches it off via its software
interface;
* The hardware switches itself off, or its firmware switches it off,
because it detected a critical fault.
* The equipment's power button is pressed;
* An upstream power supply device denies it power.
A Tango device therefore must not treat its component as under its
sole control. For example, having turned its component on, it must not
assume that the component will remain on. Rather, it must continually
*monitor* its component, and update its state to reflect changes in
component state.
Component monitoring
--------------------
Component *monitoring* is the main mechanism by which a Tango device
maintains and updates its state:
* A Tango device should not make assumptions about component state after
issuing a command. For example, after successfully telling its
component to turn on, a Tango device should not assume that the
component is on, and transition immediately to ON state. Rather, it
should wait for its monitoring of the component to provide
confirmation that the component is on; only then should it transition
to ON state. It follows that a Tango device's ``On()`` command might
complete successfully, yet the device's ``state()`` might not report
``ON`` state immediately, or for some seconds, or indeed at all.
* A Tango device also should not make assumptions about component state
when the Tango device is *initialising*. For example, in a normal
controlled startup of a telescope, an initialising Tango device might
expect to find its component switched off, and to be itself
responsible for switching the component on at the proper time.
However, this is not the only circumstance in which a Tango device
might initialise; the Tango device would also have to initialise
following a reboot of the server on which it runs. In such a case, the
component might already be switched on. Thus, at initialisation, a
Tango device should merely launch the component monitoring that will
allows the device to detect the state of the component.
Component managers
------------------
A Tango device's responsibility to monitor and control its component is
largely separate from its interface to the Tango subsystem. Therefore,
devices in this package implement component monitoring and control in a
separate *component manager*.
A component manager is responsible for:
* establishing and maintaining communication with the component. For
example:
* If the component interface is via a connection-oriented protocol
(such as TCP/IP), then the component manager must establish and
maintain a *connection* to the component;
* If the component is able to publish updates, then the component
manager would need to subscribe to those updates;
* If the component cannot publish updates, but can only respond to
requests, then the component manager would need to initiate
polling of the component.
* implementing monitoring of the component so that changes in
component state trigger callbacks that report those changes up to
the Tango device;
* implementing commands such as ``off()``, ``on()``, etc., so that
they actually tell the component to turn off, turn on, etc.
.. note:: It is highly recommended to implement your component manager,
and thoroughly test it, *before* embedding it in a Tango device.
===============
Getting started
===============
This page will guide you through the steps to writing a SKA Tango device
based on the ``ska-tango-base`` package.
Prerequisites
-------------
It is assumed here that you have a subproject repository, and have `set
up your development environment`_. The ``ska-tango-base`` package can be
installed from the EngageSKA Nexus repository:
.. code-block:: shell-session
me@local:~$ python3 -m pip install --extra-index-url https://nexus.engageska-portugal.pt/repository/pypi/simple ska-tango-base
Basic steps
-----------
The recommended basic steps to writing a SKA Tango device based on the
``ska-tango-base`` package are:
1. Write a component manager.
2. Implement command class objects.
3. Write your Tango device.
Detailed steps
--------------
Write a component manager
^^^^^^^^^^^^^^^^^^^^^^^^^
A fundamental assumption of this package is that each Tango device
exists to provide monitoring and control of some *component* of a SKA
telescope. That *component* could be some hardware, a software service
or process, or even a group of subservient Tango devices.
A *component manager* provides for monitoring and control of a
component. It is *highly recommended* to implement and thoroughly test
your component manager *before* embedding it in a Tango device.
For more information on components and component managers, see
:doc:`component_managers`.
Writing a component manager involves the following steps.
1. **Choose a subclass for your component manager.** There are several
component manager base classes, each associated with a device class.
For example,
* If your Tango device will inherit from ``SKABaseDevice``, then you
will probably want to base your component manager on the
``BaseComponentManager`` class.
* If your Tango device is a subarray, then you will want to base your
component manager on ``SubarrayComponentManager``.
.. inheritance-diagram::
ska_tango_base.base.component_manager
ska_tango_base.subarray.component_manager
ska_tango_base.csp.obs.component_manager
ska_tango_base.csp.subarray.component_manager
:parts: 1
These component managers are abstract. They specify an interface, but
leave it up to you to implement the functionality. For example,
``BaseComponentManager``'s ``on()`` command looks like this:
.. code-block:: py
def on(self):
raise NotImplementedError("BaseComponentManager is abstract.")
Your component manager will inherit these methods, and override them
with actual implementations.
.. note:: In addition to these abstract classes, there are also
reference implementations of concrete subclasses. For example, in
addition to an abstract ``BaseComponentManager``, there is also a
concrete ``ReferenceBaseComponentManager``. These reference
implementations are provided for explanatory purposes: they
illustrate how a concrete component manager might be implemented.
You are encouraged to review the reference implementations, and
adapt them to your own needs; but it is not recommended to
subclass from them.
2. **Establish communication with your component.** How you do this will
depend on the capabilities and interface of your component. for
example:
* If the component interface is via a connection-oriented protocol
(such as TCP/IP), then the component manager must establish and
maintain a *connection* to the component;
* If the component is able to publish updates, then the component
manager would need to subscribe to those updates;
* If the component cannot publish updates, but can only respond to
requests, then the component manager would need to initiate
polling of the component.
4. **Implement component monitoring.** Whenever your component changes
its state, your component manager needs to become reliably aware of
that change within a reasonable timeframe, so that it can pass this
on to the Tango device.
The abstract component managers provided already contain some helper
methods to trigger device callbacks. For example,
``BaseComponentManager`` provides a ``component_fault`` method that
lets the device know that the component has experienced a fault. You
need to implement component monitoring so that, if the component
experiences a fault, this is detected, and results in the
``component_fault`` helper method being called.
For component-specific functionality, you will need to implement the
corresponding helper methods. For example, if your component reports
its temperature, then your component manager will need to
1. Implement a mechanism by which it can let its Tango device know
that the component temperature has changed, such as a callback;
2. Implement monitoring so that this mechanism is triggered whenever
a change in component temperature is detected.
5. **Implement component control.** Methods to control the component
must be implemented; for example the component manager's ``on()``
method must be implemented to actually tell the component to turn on.
Note that component *control* and component *monitoring* are
decoupled from each other. So, for example, a component manager's
``on()`` method should not directly call the callback that tells the
device that the component is now on. Rather, the command should
return without calling the callback, and leave it to the *monitoring*
to detect when the component has changed states.
Consider, for example, a component that takes ten seconds to power
up:
1. The ``on()`` command should be implemented to tell the component
to power up. If the component accepts this command without
complaint, then the ``on()`` command should return success. The
component manager should not, however, assume that the component
is now on.
2. After ten seconds, the component has powered up, and the component
manager's monitoring detects that the component is on. Only then
should the callback be called to let the device know that the
component has changed state, resulting in a change of device state
to ``ON``.
.. note:: A component manager may maintain additional state, and support
additional commands, that do not map to its component. That is, a
call to a component manager needs not always result in a call to the
underlying component. For example, a subarray's component manager may
implement its ``assign_resources`` method simply to maintain a record
(within the component manager itself) of what resources it has, so
that it can validate arguments to other methods (for example, check
that arguments to its ``configure`` method do not require access to
resources that have not been assigned to it). In this case, the call
to the component manager's ``assign_resources`` method would not
result in interaction with the component; indeed, the component may
not even possess the concepts of *resources* and *resource
assignment*.
Implement command class objects
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Tango device command functionality is implemented in command *classes*
rather than methods. This allows for:
* functionality common to many classes to be abstracted out and
implemented once for all. For example, there are many commands
associated with transitional states (*e.g.* ``Configure()`` command
and ``CONFIGURING`` state, ``Scan()`` command and ``SCANNING`` state,
*etc.*). Command classes allow us to implement this association once
for all, and to protect that implementation from accidental overriding
by command subclasses.
* testing of commands independently of Tango. For example, a Tango
device's ``On()`` command might only need to interact with the
device's component manager and its operational state model. As such,
in order to test the correct implementation of that command, we only
need a component manager and an operational state model. Thus, we can
test the command without actually instantiating the Tango device.
Writing a command class involves the following steps.
1. **Do you really need to implement the command?** If the command to be
implemented is part of the Tango device you will inherit from,
perhaps the current implementation is exactly what you need.
For example, the ``SKABaseDevice`` class's implementation of the
``On()`` command simply calls its component manager's ``on()``
method. Maybe you don't need to change that; you've implemented your
component manager's ``on()`` method, and that's all there is to do.
2. **Choose a command class to subclass.**
* If the command to be implemented is part of the device you will
inherit from (but you still need to override it), then you would
generally subclass the base device's command class. For example, if
if you need to override ``SKABaseDevice``'s ``Standby`` command,
then you would subclass ``SKABaseDevice.StandbyCommand``.
* If the command is a new command, not present in the base device
class, then you will want to inherit from one or more command
classes in the :py:mod:`ska_tango_base.commands` module.
.. inheritance-diagram::
ska_tango_base.commands.BaseCommand
ska_tango_base.commands.StateModelCommand
ska_tango_base.commands.ResponseCommand
ska_tango_base.commands.CompletionCommand
ska_tango_base.commands.ObservationCommand
:parts: 1
3. **Implement class methods.**
* In many cases, you only need to implement the ``do()`` method.
* To constrain when the command is allowed to be invoked, override
the ``is_allowed()`` method.
Write your Tango device
^^^^^^^^^^^^^^^^^^^^^^^
Writing the Tango device involves the following steps:
1. **Select a device class to subclass.**
.. inheritance-diagram::
ska_tango_base.SKAAlarmHandler
ska_tango_base.SKACapability
ska_tango_base.SKALogger
ska_tango_base.SKAMaster
ska_tango_base.SKATelState
ska_tango_base.base.SKABaseDevice
ska_tango_base.obs.SKAObsDevice
ska_tango_base.subarray.SKASubarray
ska_tango_base.csp.CspSubElementMaster
ska_tango_base.csp.CspSubElementObsDevice
ska_tango_base.csp.CspSubElementSubarray
:top-classes: ska_tango_base.base.SKABaseDevice
:parts: 1
2. **Register your component manager.** This is done by overriding the
``create_component_manager`` class to return your component manager
object:
.. code-block:: py
def create_component_manager(self):
return AntennaComponentManager(
self.op_state_model, logger=self.logger
)
3. **Implement commands.** You've already written the command classes.
There is some boilerplate to ensure that the Tango command methods
invoke the command classes:
1. Registration occurs in the ``init_command_objects`` method, using
calls to the ``register_command_object`` helper method. Implement
the ``init_command_objects`` method:
.. code-block:: py
def init_command_objects(self):
super().init_command_objects()
self.register_command_object(
"DoStuff", self.DoStuffCommand(self.component_manager, self.logger)
)
self.register_command_object(
"DoOtherStuff", self.DoOtherStuffCommand(
self.component_manager, self.logger
)
)
2. Any new commands need to be implemented as:
.. code-block:: py
@command(dtype_in=..., dtype_out=...)
def DoStuff(self, argin):
command = self.get_command_object("DoStuff")
return command(argin)
or, if the command does not take an argument:
.. code-block:: py
@command(dtype_out=...)
def DoStuff(self):
command = self.get_command_object("DoStuff")
return command()
Note that these two examples deliberately push all SKA business
logic down to the command class (at least) or even the component
manager. It is highly recommended not to include SKA business
logic in Tango devices. However, Tango-specific functionality can
and should be implemented directly into the command method. For
example, many SKA commands accept a JSON string as argument, as a
workaround for the fact that Tango commands cannot accept more
than one argument. Since this use of JSON is closely associated
with Tango, we might choose to unpack our JSON strings in the
command method itself, thus leaving our command objects free of
JSON:
.. code-block:: py
@command(dtype_in=..., dtype_out=...)
def DoStuff(self, argin):
args = json.loads(argin)
command = self.get_command_object("DoStuff")
return command(args)
.. _set up your development environment: https://developer.skatelescope.org/en/latest/tools/tango-devenv-setup.html
\ No newline at end of file
===============
Developer Guide
===============
.. toctree::
Getting started<getting_started>
Components and component managers<component_managers>
docs/source/images/AdminModeStateMachine_autogenerated.png

62.1 KiB

docs/source/images/CspSubElementObsDeviceStateMachine_autogenerated.png

153 KiB

docs/source/images/ObservationStateMachine_autogenerated.png

233 KiB

docs/source/images/OperationStateMachine_autogenerated.png

245 KiB

docs/source/images/OperationStateMachine_coupled.png

40.6 KiB

0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment