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.