Initialising your SKA Tango device
Prefer init_device to __init__
Note
SKABaseDevice provides a deprecated mechanism to provide device initialisation by overriding an InitCommand object.
In order to use init_device as described here you must set InitCommand to None to acknowledge the deprecation.
See SKA command objects deprecated for more details.
In general, initialisation of a Tango device should be done in the init_device() method as this allows clients to “re-initialise” the device via the built-in Init() command. For example, the following device starts a background thread and opens a socket connect
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 super().init_device()! Typically, this should be done before your code.
Clean up resources in delete_device
All resources allocated during init_device() should be reclaimed during the delete_device() method. In other words, all threads must be joined, files/sockets closed, Tango event subscriptions must be unsubscribed, 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.
Warning
Caution must be taken when relying on the python interpreter to reclaim PyTango objects as the C++ destructor will run for these objects, which may do things beyond just reclaiming memory. For example, if a tango.DeviceProxy is destroyed all Tango event subscriptions that were created by that device proxy will be unsubscribed. If the tango.DeviceProxy was being kept alive by a complex python object with reference cycles this unsubscription will be done at an unknown time by the python garbage collector.
For the example device above, we need to join the thread and close the socket:
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 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 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.
Call init_completed when inheriting from SKABaseDevice
When inheriting from SKABaseDevice, the super().init_device() will set the device state to INIT. You are then responsible for calling init_completed() once initialisation has finished. This will then transition the device state according to the operational state model.
It is possible to call init_completed() some time later if your device has a long running initialisation process.
For example, the following device calls init_completed() immediately from its init_device():
class MyDevice(SKABaseDevice[MyComponentManager]):
InitCommand = None
def init_device(self) -> None:
super().init_device()
self.init_completed()