4.4.4. StdCmds Interface (cut_rad_hello/src/cut_rad_hello/stdcmdsfacade.py)

stdcmdsfacade.py is a new file, and will encapsulate all communications to the hellocii application. This class will interface against a GUI, so it is imperative that we use Signals and Slots, and that we connect those to our UI.

4.4.4.1. Imports

The most important section are the ModHellociiif imports. There are the library produced by the ICD compilation for python. MAL sets the name as: Mod<Package_name>.<Package_name>. Please notice that MAL uses uses the <PackageName> but uppercases the first letter. This python module contains at this level the structures and exceptions definitions, and at the Mod<PackageName>.<PackageName>.<interfaceName> level the <StdCmdsAsync> class, which we use in this file.

from ModHellociiif.Hellociiif.StdCmds import StdCmdsAsync
from ModHellociiif.Hellociiif import LogInfo

4.4.4.2. Class Definition and Initializator

The class StdCmdsFacade inherits from MalAdapter. MalAdapter is in charge of the connection logic and exposes the connection status through a Property, which in turn has a Slot to change the connection state, and a Signal that indicates if the connection status has changed.

In short, it exposes a MAL Interface in a Qt compatible manner.

class StdCmdsFacade(MalAdapter):

    def __init__(self, uri: str):
        '''
        Initializator for a new `StdCmdsFacade`. It inherits from MalAdapter,
        which provides all the connection logic ready to be used.

        We mainly interact with MalAdapter through `get_interface()`
        `get_mal()` and `get_factory()`. We do not keep references to them.

        This class exposes Slots that use the elt.cut.task.Task class to
        execute long-running operations in a separate thread. Each Slot
        is a method in the hellociiif interface.

        :param uri: URI for the hellocii MAL reply request endpoint
        '''
        MalAdapter.__init__(self, uri, StdCmdsAsync)

We call explicitly its parent initializator and pass the URI of the hellocii endpoint, and the class of the MAL generated interface. This is enough information for the MalAdapter to create the connection to the endpoint.

4.4.4.3. Interface Methods with no Arguments

The implementation of these methods is simple, lets see for example the one corresponding to the Init().

@TraceIt()
@Slot()
def init(self, result_slot=None, error_slot=None):
    '''
    This method executes the Init command on the server.
    It uses the Asynchronous interface.
    It will wait or the future object to has its result, and then
    it will call the slot function with the result as argument.

    Parameters
    -----------
    result_slot: QtCore.QSlot
        Slot method that will be triggered when the result is available.
    error_slot: QtCore.QSlot
        Slot method that will be triggered when an error is detected.

    Return
    -------
    None
    '''
    if(not self.isConnected()):
        self.error('Application is not connected to TrkLsv. \
                    Please connect first.')
        return
    task = Task(self.get_interface().Init)
    if result_slot is not None:
        task.signals.result.connect(result_slot)
    if error_slot is not None:
        task.signals.error.connect(error_slot)
    task.signals.finished.connect(self.thread_complete)
    task.start()

The decorators (@TraceIt and samp:@Slot) we use indicate that this has to be traced, and that it is a Qt Slot. Notice that this Slot has not arguments.

This init() method check first if the class isConnected() before proceeding.

Then it creates a Task that calls the Init() from the interface its parent got for us.

At this point, the reader may wonder where is the connection handled? The logic is provided by MalAdapter, and it is triggered at the start of the application in applicationwindow.py. But we will see more details on that later.

In this example, we are setting three of the Task Signals. We are using the result_slot, the error_slot and the finished_slot.

At the end of the method, we start() the task. Everything else is handled in an asynchronous manner by the slots we specified.

4.4.4.4. Interface Method with Argument

This method is the only one that uses arguments in the Hellociiif Interface. We want to shield the rest of the code from Structures that are specific to the Interface module, so this Slot accepts strings as arguments, aside from the Slots for the feedback on the operation.

@Slot(str, str)
def set_log_level(self, log_level: str, logger: str, result_slot=None, error_slot=None):
    '''
    This method executes the SetLogLevel command on the server.
    It uses the Asynchronous interface.
    It will wait or the future object to has its result, and then
    it will call the slot function with the result as argument.

    Parameters
    -----------
    log_level: str
        Log Level
    logger: str
        Logger to be affected by this command
    result_slot: QtCore.QSlot
        Slot method that will be triggered when the result is available.
    error_slot: QtCore.QSlot
        Slot method that will be triggered when an error is detected.

    Return
    -------
    None

    '''
    if(not self.isConnected()):
        self.error('Application is not connected to TrkLsv. \
                    Please connect first.')
        return
    loginfo = self.get_mal().createDataEntity(LogInfo)
    loginfo.setLevel(log_level)
    loginfo.setLogger(logger)
    task = Task(self.get_interface().SetLogLevel, loginfo)
    if result_slot is not None:
        task.signals.result.connect(result_slot)
    if error_slot is not None:
        task.signals.error.connect(error_slot)
    task.signals.finished.connect(self.thread_complete)
    task.start()

SetLogLevel() uses a LogInfo Structure. We need to use the MAL to create a data entity of the specific type. Once we have it, we can fill its members with the required strings.

We pass this structure as the second argument in the initializator of the Task class.