4. Communication Facades

The communication facades module (ifw.wdglib.comm) provides Python wrappers around the ELT MAL (CII Middleware Abstraction Layer) interfaces. They abstract the details of MAL connection management, asynchronous command execution, and result/error delivery, allowing widgets and application code to interact with subsystems in a consistent signal-based manner.

This chapter covers:

  • The ConnectionManager singleton pattern

  • Individual facades and their commands

  • Result and error handling with signals

  • How to connect facades and use them from widgets

4.1. Connection Manager

The ConnectionManager is a thread-safe singleton that creates and caches facade instances by MAL URI. Its purpose is to ensure that multiple widgets and application components share the same underlying MAL connection to a given endpoint, avoiding redundant network connections and keeping connection state consistent.

4.1.1. Using the Connection Manager

The manager is instantiated once. With auto_connect enabled (the default), each get_* call will automatically connect the facade if it is not already connected:

from ifw.wdglib.comm import ConnectionManager
from ifw.wdglib.comm import StdCmdsFacade
from ifw.wdglib.comm import DcsCmdsFacade

base_uri = "zpb.rr://127.0.0.1:12081"

conn_mgr = ConnectionManager()
conn_mgr.auto_connect = True  # or False for explicit connection

std_cmds = conn_mgr.get_stdcmds(f"{base_uri}/StdCmds")
dcs_cmds = conn_mgr.get_dcscmds(f"{base_uri}/DcsCmds")

Important

Create the ConnectionManager at the very beginning of your QMainWindow application, before any widgets are instantiated. Set auto_connect to True or False once, at this point, according to your application needs. When auto_connect is False, call conn_mgr.connect_all() after retrieving all needed facades to establish connections explicitly. Because ConnectionManager is a singleton, widgets instantiated later will share the same connection state when they retrieve facades via get_* calls. This ensures all facades are ready and connected before any widget attempts to use them.

Do not set auto_connect inside a custom widget. This flag must only be managed at the application level.

Each facade type has its own lookup method:

Method

Returns

MAL Interface

get_stdcmds(uri: str)

StdCmdsFacade

STD IF

get_dcscmds(uri: str)

DcsCmdsFacade

DCS IF

get_reccmds(uri: str)

RecCmdsFacade

REC IF

get_fcfcmds(uri: str)

FcfCmdsFacade

FCF

get_syssupcmds(uri: str)

SysSupCmdsFacade

SUP (system supervisor)

get_custom_facade(uri: str)

Custom MalAdapter (CUT)

Any

Once a facade is cached for a given URI, subsequent calls to the same get_* method with the same URI return the existing instance. This means that if a widget retrieves a facade that the main window already holds, they share the exact same connection.

4.1.2. Bulk Connection Management

You can connect or disconnect all managed facades at once:

conn_mgr.connect_all()
conn_mgr.disconnect_all()

Or inspect all connections:

all = conn_mgr.get_all_connections()
for uri, facade in all.items():
    print(f"{uri}: {facade.is_connected()}")

4.1.3. Custom Facades

For MAL interfaces not covered by the built-in facades, use get_custom_facade(). The adapter class (a subclass of MalAdapter from the CUT library) is required on the first call for a given URI:

from ifw.wdglib.comm import ConnectionManager
from my_module import MyCustomFacade

conn_mgr = ConnectionManager()
my_facade = conn_mgr.get_custom_facade(
    f"{base_uri}/MyEndpoint",
    adapter=MyCustomFacade,
)

4.2. Facade Commands and Signals

All facades follow the same pattern for executing commands and delivering results. They are built on the MalAdapter base class from the CUT (Control UI Toolkit) library, which provides connection management (set_connection(), is_connected(), connectionChanged signal).

4.2.1. Common Signals

Every facade emits the following signals:

  • command_result (object) – emitted when a command completes successfully with the MAL reply object

  • command_error (tuple) – emitted when a command fails; the tuple contains (exctype, value, traceback.format_exc())

  • command_finished – emitted when a command task has fully completed

  • connectionChanged (bool) – emitted when the MAL connection state changes

4.2.2. Command Execution Pattern

Each facade command method accepts an optional result_slot and error_slot parameter. When provided, these slots are connected to the underlying task signals so the caller receives the result or error directly:

from taurus.external.qt.QtCore import Slot

class MyWidget(QWidget, Logger):
    @Slot(object)
    def _on_result(self, reply):
        print(f"Got result: {reply}")

    @Slot(object, object, object)
    def _on_error(self, exctype, value, tb):
        print(f"Error: {value}")

    def some_button_clicked(self):
        self._stdcmds.get_state(
            result_slot=self._on_result,
            error_slot=self._on_error,
        )

If no slots are provided, the facade still logs the result or error internally, and the command_result / command_error signals are emitted. You can connect to them globally:

std_cmds.command_result.connect(my_result_handler)
std_cmds.command_error.connect(my_error_handler)

Note

All commands are executed asynchronously in a separate thread using elt.cut.task.Task from the CUT library. Calling a facade command does not block the Qt event loop.

4.3. Available Facades

4.3.1. STD IF Facade (StdCmdsFacade)

The StdCmdsFacade wraps the Standard Interface (STD IF) MAL interface. It provides the set of commands available on every ELT subsystem:

  • init() – Initialize the subsystem

  • reset() – Reset the subsystem

  • enable() – Enable the subsystem

  • disable() – Disable the subsystem

  • stop() – Stop the subsystem

  • exit() – Exit the subsystem

  • get_state() – Get the current state machine state

  • get_status() – Get the subsystem status

  • get_version() – Get the subsystem version

  • set_log_level(loginfo: ModStdif.Stdif.LogInfo) – Set the logging level

The create_log_info() helper builds a LogInfo data entity for set_log_level():

from ifw.wdglib.comm import ConnectionManager
from ifw.wdglib.comm import StdCmdsFacade

conn_mgr = ConnectionManager()
std_cmds = conn_mgr.get_stdcmds("zpb.rr://127.0.0.1:12081/StdCmds")

log_info = std_cmds.create_log_info(level="DEBUG", logger="my.logger")
std_cmds.set_log_level(log_info)

4.3.2. DCS IF Facade (DcsCmdsFacade)

The DcsCmdsFacade wraps the Detector Control System Interface (DCS IF). Commands:

  • start(start_properties: ModDcsif.Dcsif.StartProperties) – Start the detector

  • abort() – Abort ongoing operation

  • execute(method: ModDcsif.Dcsif.Method) – Execute a DCS method

  • setup(parameter) – Send setup parameters

  • recover() – Recover from error state

  • get_config(parameters) – Get configuration

  • set_config(parameter_dict: dict) – Set configuration

  • write_pars(parameter_dict: dict) – Write parameters

  • get_diagnostics() – Get diagnostics

Helper methods create_start_properties() and create_method() build the corresponding MAL data entities:

from ifw.wdglib.comm import ConnectionManager
from ifw.wdglib.comm import DcsCmdsFacade
from ModDcsif.Dcsif import StartTriggerTypes

conn_mgr = ConnectionManager()
dcs_cmds = conn_mgr.get_dcscmds("zpb.rr://127.0.0.1:12081/DcsCmds")

props = dcs_cmds.create_start_properties(
    abs_time=0.0,
    trigger=StartTriggerTypes.NotUsed,
    info="Manual start",
)
dcs_cmds.start(props)

method = dcs_cmds.create_method(name='tsu', parameters='start')
dcs_cmds.execute(method)

4.3.3. REC IF Facade (RecCmdsFacade)

The RecCmdsFacade wraps the Data Recorder Interface (REC IF). Commands:

  • rec_start(rec_properties: ModRecif.Recif.RecProperties) – Start a recording session

  • rec_stop() – Stop the current recording

  • rec_abort() – Abort the recording session

  • rec_pause() – Pause the recording

  • rec_continue() – Continue a paused recording

  • rec_status(rec_id: str) – Get recording status by ID

The create_rec_properties() helper builds a RecProperties data entity:

from ifw.wdglib.comm import ConnectionManager
from ifw.wdglib.comm import RecCmdsFacade

conn_mgr = ConnectionManager()
rec_cmds = conn_mgr.get_reccmds("zpb.rr://127.0.0.1:12081/RecCmds")

rec_props = rec_cmds.create_rec_properties(
    id="rec1",
    info="Test recording",
    abs_time=0.0,
    publishers=["publisher1"],
)
rec_cmds.rec_start(rec_props)

4.3.4. FCF Facade (FcfCmdsFacade)

The FcfCmdsFacade wraps the ELT ICS Function Control Framework (FCF). It provides device-level control commands for shutters, lamps, motors, derotators, piezo devices, and I/O devices.

Common commands:

  • recover() – Recover devices from error state

  • dev_names() – List available device names

  • dev_info() – Get general device information

  • dev_status(devices: Optional[List[str]] = None) – Get device status

  • get_config() – Get server configuration

  • setup(payload: Optional[List[str]] = None) – Send setup payload to devices

  • simulate(devices: Optional[List[str]] = None) – Put devices in simulation mode

  • stop_sim(devices: Optional[List[str]] = None) – Stop device simulation

  • ignore(devices: Optional[List[str]] = None) – Ignore devices (exclude from operational status)

  • hw_init(devices: Optional[List[str]] = None) – Initialize hardware controllers

  • hw_enable(devices: Optional[List[str]] = None) – Enable hardware controllers

  • hw_disable(devices: Optional[List[str]] = None) – Disable hardware controllers

  • hw_reset(devices: Optional[List[str]] = None) – Reset hardware controllers

Shutter and lamp control:

  • open(device: str) – Open a shutter

  • close(device: str) – Close a shutter

  • switch_on(device: str, intensity: float = 100.0, time: int = 0) – Switch on a lamp

  • switch_off(device: str) – Switch off a lamp

Motor and derotator control:

  • move_abs(device: str, position: float = 0.0) – Move motor to absolute position

  • move_by_name(device: str, named_position: str) – Move motor to named position

  • start_track(device: str, mode: str) – Start derotator tracking

  • stop_track(device: str) – Stop derotator tracking

  • track_offset(device: str, offset: float) – Send tracking offset

  • set_tip_tilt(device: str, tip: float, tilt: float) – Set piezo tip/tilt

  • set_custom(device: str, parameters: str) – Set custom device parameters

I/O device control (fcfcli-style helpers):

  • write_digital(devname: str, channel: str, value: bool) – Write to a digital I/O channel

  • write_analog(devname: str, channel: str, value: float) – Write to an analog I/O channel

  • write_integer(devname: str, channel: str, value: int) – Write to an integer I/O channel

Example:

from ifw.wdglib.comm import ConnectionManager
from ifw.wdglib.comm import FcfCmdsFacade

conn_mgr = ConnectionManager()
fcf_cmds = conn_mgr.get_fcfcmds("zpb.rr://127.0.0.1:12081/AppCmds")

# Get device status for all devices
fcf_cmds.dev_status()

# Open a shutter
fcf_cmds.open("shutter1")

# Move a motor to absolute position
fcf_cmds.move_abs("motor1", position=0.5)

4.3.5. SysSup Facade (SysSupCmdsFacade)

The SysSupCmdsFacade wraps the SUP (System Supervisor) interface. It allows control of subsystem lifecycle and observation mode through the system supervisor:

from ifw.wdglib.comm import ConnectionManager
from ifw.wdglib.comm import SysSupCmdsFacade

conn_mgr = ConnectionManager()
syssup_cmds = conn_mgr.get_syssupcmds("zpb.rr://127.0.0.1:12081/AppCmds")

syssup_cmds.init(subsys="ccf")
syssup_cmds.enable(subsys="ccf")
syssup_cmds.set_obmode(mode="SCIENCE")

Available commands:

  • init(subsys: str) – Initialize a subsystem

  • enable(subsys: str) – Enable a subsystem

  • disable(subsys: str) – Disable a subsystem

  • reset(subsys: str) – Reset a subsystem

  • set_obmode(mode: str) – Set observation mode

  • set_access(subsys: str, access: bool) – Grant or revoke subsystem access

4.4. Using Facades in Widgets

Widgets typically inherit from BaseMalWidget, which centralizes facade registration and connection monitoring. See the BaseMalWidget chapter for full details.

A typical widget registers its facades in _setup_facades():

from ifw.wdglib.comm import StdCmdsFacade
from ifw.wdglib.widgets.common import BaseMalWidget

class MyWidget(BaseMalWidget):
    def __init__(self, parent, base_uri, dcs_db_prefix):
        super().__init__(parent, base_uri)
        self._dcs_db_prefix = dcs_db_prefix
        self._setup_facades()
        # ... UI setup, remove_bgrole(), _update_db_models(), _init_gui()

    def _setup_facades(self) -> None:
        self._stdcmds = self._conn_mgr.get_stdcmds(
            f"{self._base_uri}/StdCmds"
        )
        self.register_facade(self._stdcmds)
        self._dcscmds = self._conn_mgr.get_dcscmds(
            f"{self._base_uri}/DcsCmds"
        )
        self.register_facade(self._dcscmds)

    @Slot(object)
    def _error_slot(self, exception_err):
        exctype, value, tb = exception_err
        self.error(f"Facade error: {value}")

This pattern ensures that:

  1. The widget shares facade instances with the rest of the application (via the singleton ConnectionManager)

  2. The widget is automatically disabled when any connection is lost

  3. Errors from facade commands are handled through a consistent logger call