.. _init_guidelines: ================================== Initialising your SKA Tango device ================================== Prefer init_device to __init__ ------------------------------ .. note :: :py:class:`~ska_tango_base.base.base_device.SKABaseDevice` provides a deprecated mechanism to provide device initialisation by overriding an :py:class:`~ska_tango_base.base.base_device.SKABaseDevice.InitCommand` object. In order to use :code:`init_device` as described here you must set :py:class:`!InitCommand` to :code:`None` to acknowledge the deprecation. See :ref:`migrate-1-4-cmd-obj` for more details. In general, initialisation of a Tango device should be done in the :py:func:`!init_device` method as this allows clients to "re-initialise" the device via the built-in :py:func:`!Init` command. For example, the following device starts a background thread and opens a socket connect .. code-block:: py class MyDevice(LRCMixin, SKADevice): HardwareHost = device_property(dtype=str, mandatory=True) HardwarePort = device_property(dtype=int, mandatory=True) def init_device(self) -> None: # device properties are read from the Tango database here: super().init_device() # TODO: Handle connection failure self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket.connect((self.HardwareHost, self.HardwarePort)) self.stop_event = threading.Event # To signal to the background thread to stop self.background_thread = threading.Thread(target=self._run_background_thread) self.background_thread.start() def _run_background_thread(self) -> None: # All threads should be made an omnithread: with tango.EnsureOmniThread(): ... .. tip:: Don't forget to call :code:`super().init_device()`! Typically, this should be done *before* your code. Clean up resources in delete_device ----------------------------------- All resources allocated during :py:func`!init_device` should be reclaimed during the :py:func:`!delete_device` method. In other words, all threads must be joined, files/sockets closed etc. As we are using python, we don't have to worry about the python objects themselves being reclaimed as this will be handled by the python interpreter automatically. For the example device above, we need to join the thread and close the socket: .. code :: py class MyDevice(LRCMixin, SKADevice): ... def delete_device(self) -> None: self.stop_event.set() with self.allow_internal_threads(): self.background_thread.join() self.socket.close() super().delete_device() .. tip:: Don't forget to call :code:`super().delete_device()`! Typically, this should be done *after* your code. .. tip:: When joining a thread that needs to interact with the Tango device, use :py:func:`~ska_tango_base.software_bus.SignalBusMixin.allow_internal_threads` to avoid a deadlock. This will release the Tango device monitor lock, allowing the thread being joined to acquire the monitor lock, but it will not allow new client requests to start. Provide an on_new_shared_bus method when initialising objects that use the bus ------------------------------------------------------------------------------ The :py:func:`~ska_tango_base.software_bus.SharingObserver.on_new_shared_bus` method is called after the signal bus object has been created, but before :py:class:`~ska_tango_base.software_bus.SharingObserver` sub-objects are located and before the signal bus thread is started. This is the correct time to create :py:class:`~ska_tango_base.software_bus.SharingObserver` sub-objects, or objects that want to use the signal bus. For example, the following device server adds dynamic :py:class:`~ska_tango_base.software_bus.attribute_from_signal` objects: .. code :: py class MyDevice(LRCMixin, SKADevice): ... my_signal = Signal[int](stored=Ture, initial_value=0) def on_new_shared_bus(self) -> None: super().on_new_shared_bus() self.add_attribute(attribute_from_signal(self.__class__.my_signal)) .. tip:: Don't forget to call :code:`super().on_new_shared_bus()`! Typically, this should be done *before* your code. Call init_completed when inheriting from SKABaseDevice ------------------------------------------------------ When inheriting from :py:class:`~ska_tango_base.base.base_device.SKABaseDevice`, the :code:`super().init_device()` will set the device state to :code:`INIT`. You are then responsible for calling :py:func:`~ska_tango_base.base.base_device.SKABaseDevice.init_completed()` once initialisation has finished. This will then transition the device state according to the operational state model. It is possible to call :py:func:`~ska_tango_base.base.base_device.SKABaseDevice.init_completed()` some time later if your device has a long running initialisation process. For example, the following device calls :py:func:`~ska_tango_base.base.base_device.SKABaseDevice.init_completed()` immediately from its :py:func:`!init_device`: .. code :: py class MyDevice(SKABaseDevice[MyComponentManager]): InitCommand = None def init_device(self) -> None: super().init_device() self.init_completed()