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
ConnectionManagersingleton patternIndividual 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 |
|---|---|---|
|
|
STD IF |
|
|
DCS IF |
|
|
REC IF |
|
|
FCF |
|
|
SUP (system supervisor) |
|
Custom |
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 objectcommand_error(tuple) – emitted when a command fails; the tuple contains(exctype, value, traceback.format_exc())command_finished– emitted when a command task has fully completedconnectionChanged(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 subsystemreset()– Reset the subsystemenable()– Enable the subsystemdisable()– Disable the subsystemstop()– Stop the subsystemexit()– Exit the subsystemget_state()– Get the current state machine stateget_status()– Get the subsystem statusget_version()– Get the subsystem versionset_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 detectorabort()– Abort ongoing operationexecute(method: ModDcsif.Dcsif.Method)– Execute a DCS methodsetup(parameter)– Send setup parametersrecover()– Recover from error stateget_config(parameters)– Get configurationset_config(parameter_dict: dict)– Set configurationwrite_pars(parameter_dict: dict)– Write parametersget_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 sessionrec_stop()– Stop the current recordingrec_abort()– Abort the recording sessionrec_pause()– Pause the recordingrec_continue()– Continue a paused recordingrec_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 statedev_names()– List available device namesdev_info()– Get general device informationdev_status(devices: Optional[List[str]] = None)– Get device statusget_config()– Get server configurationsetup(payload: Optional[List[str]] = None)– Send setup payload to devicessimulate(devices: Optional[List[str]] = None)– Put devices in simulation modestop_sim(devices: Optional[List[str]] = None)– Stop device simulationignore(devices: Optional[List[str]] = None)– Ignore devices (exclude from operational status)hw_init(devices: Optional[List[str]] = None)– Initialize hardware controllershw_enable(devices: Optional[List[str]] = None)– Enable hardware controllershw_disable(devices: Optional[List[str]] = None)– Disable hardware controllershw_reset(devices: Optional[List[str]] = None)– Reset hardware controllers
Shutter and lamp control:
open(device: str)– Open a shutterclose(device: str)– Close a shutterswitch_on(device: str, intensity: float = 100.0, time: int = 0)– Switch on a lampswitch_off(device: str)– Switch off a lamp
Motor and derotator control:
move_abs(device: str, position: float = 0.0)– Move motor to absolute positionmove_by_name(device: str, named_position: str)– Move motor to named positionstart_track(device: str, mode: str)– Start derotator trackingstop_track(device: str)– Stop derotator trackingtrack_offset(device: str, offset: float)– Send tracking offsetset_tip_tilt(device: str, tip: float, tilt: float)– Set piezo tip/tiltset_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 channelwrite_analog(devname: str, channel: str, value: float)– Write to an analog I/O channelwrite_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 subsystemenable(subsys: str)– Enable a subsystemdisable(subsys: str)– Disable a subsystemreset(subsys: str)– Reset a subsystemset_obmode(mode: str)– Set observation modeset_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:
The widget shares facade instances with the rest of the application (via the singleton
ConnectionManager)The widget is automatically disabled when any connection is lost
Errors from facade commands are handled through a consistent logger call