diff --git a/src/calng/CalibrationManager.py b/src/calng/CalibrationManager.py index 1844e030a94c639133676a5dc6f05ccfce25858c..bed6a8307cd225cc7a0d4d4c2002cd2086c888a5 100644 --- a/src/calng/CalibrationManager.py +++ b/src/calng/CalibrationManager.py @@ -27,7 +27,7 @@ from karabo.middlelayer import ( Int16, UInt16, UInt32, Bool, Float, Double, String, VectorString, VectorHash, background, call, callNoWait, setNoWait, sleep, instantiate, slot, coslot, - get_property, getDevice, getTopology, getConfiguration, + get_property, getDevice, getSchema, getTopology, getConfiguration, getConfigurationFromPast, getConfigurationFromName, getInstanceInfo, waitUntil) @@ -287,11 +287,18 @@ class CalibrationManager(DeviceClientBase, Device): daqPolicy=DaqPolicy.OMIT) @slot - def requestScene(self, params): + async def requestScene(self, params): name = params.get('name', default='overview') payload = Hash('success', True) - + arbiter_schema = None + if self.arbiterDevice.value: + try: + arbiter_schema = await wait_for( + getSchema(self.arbiterDevice.value), 3) + except AsyncTimeoutError: + # scene generation will get name and None + pass if name == 'overview': # Assumes there are correction devices known to manager scene_data = scenes.manager_device_overview( @@ -301,6 +308,8 @@ class CalibrationManager(DeviceClientBase, Device): correction_device_schema=self._correction_device_schema, daq_device_ids=self._daq_device_ids, domain_device_ids=self._domain_device_ids, + arbiter_id=self.arbiterDevice.value, + arbiter_schema=arbiter_schema, ) payload = Hash('success', True, 'name', name, 'data', scene_data) @@ -427,6 +436,19 @@ class CalibrationManager(DeviceClientBase, Device): accessMode=AccessMode.INITONLY, assignment=Assignment.MANDATORY) + arbiterDevice = String( + displayedName='Arbiter device', + defaultValue='', + description='Device ID for a frame selection arbiter. For now, this is ' + 'used only to dynamically update the overview scene*. To actually use ' + 'the arbiter to filter frames, you must configure the group matchers ' + 'accordingly. Note that the arbiter can be one of the arbiter types ' + 'from calng or a lit frame finder. *If the arbiter is not up, the ' + 'manager cannot generate the frame selection part of the scene - try ' + 'requesting the scene again with the arbiter around.', + accessMode=AccessMode.RECONFIGURABLE, + assignment=Assignment.OPTIONAL) + webserverApi = Node( WebserverApiNode, displayedName='Webserver API', diff --git a/src/calng/FrameSelectionArbiter.py b/src/calng/FrameSelectionArbiter.py index aebb79232e0bbdd12b81ea3e6757957ac148c8e6..74871dfdb9fc32cb84ecc7a219ae68e3e2316133 100644 --- a/src/calng/FrameSelectionArbiter.py +++ b/src/calng/FrameSelectionArbiter.py @@ -270,6 +270,32 @@ class SimpleFrameSelectionArbiter(BaseFrameSelectionArbiter): if conf.has("frameSelection.kernelParameters"): self.kernel.reconfigure(conf["frameSelection.kernelParameters"]) + def requestScene(self, params): + scene_name = params.get("name", default="") + if scene_name == "trainMatcherScene": + params["name"] = "scene" + return super().requestScene(params) + if scene_name != "overview": + self.log.INFO( + f"Unexpected scene name {scene_name} requested, " + "will just return overview" + ) + payload = Hash("name", scene_name, "success", True) + payload["data"] = scenes.simple_arbiter_overview( + device_id=self.getInstanceId(), + schema=self.getFullSchema(), + ) + self.reply( + Hash( + "type", + "deviceScene", + "origin", + self.getInstanceId(), + "payload", + payload, + ) + ) + def on_matched_data(self, train_id, sources): num_frames = self._guess_number_of_frames(sources) diff --git a/src/calng/scenes.py b/src/calng/scenes.py index e73d6a4e3fed015bf1eb96ffff98a847c9ebbe02..f3f136c8a7527a9b8db1b35eab21119d7fa01a76 100644 --- a/src/calng/scenes.py +++ b/src/calng/scenes.py @@ -28,6 +28,7 @@ from karabo.common.scenemodel.api import ( LineEditModel, NDArrayGraphModel, SceneTargetWindow, + StickerModel, TableElementModel, TrendGraphModel, UnknownWidgetDataModel, @@ -245,6 +246,61 @@ class ProcessingDeviceStatus(VerticalLayout): self.children.append(status_log) +class CompactArbiterOverview(VerticalLayout): + def __init__(self, device_id, schema_hash): + super().__init__(padding=0) + self.children.append( + HorizontalLayout( + DeviceSceneLinkModel( + text=device_id.split("/")[-1], + keys=[f"{device_id}.availableScenes"], + target="overview", + target_window=SceneTargetWindow.Dialog, + width=5 * BASE_INC, + height=BASE_INC, + ), + DisplayStateColorModel( + show_string=True, + keys=[f"{device_id}.state"], + width=5 * BASE_INC, + height=BASE_INC, + ), + ) + ) + if schema_hash.has("frameSelection"): + # it's probably a frame selection arbiter + if schema_hash.has("frameSelection.decision"): + # it's the advanced version + self.children.extend( + [ + EditableRow(device_id, schema_hash, "frameSelection.decision"), + TableElementModel( + keys=[f"{device_id}.frameSelection.plan"], + klass="EditableTableElement", + width=20 * BASE_INC, + height=10 * BASE_INC, + ), + DisplayTextLogModel( + keys=[f"{device_id}.status"], + width=20 * BASE_INC, + height=8 * BASE_INC, + ), + ] + ) + else: + # it's the simple version + self.children.append( + recursive_editable( + device_id, + schema_hash, + "frameSelection", + ) + ) + elif schema_hash.has("inputBunchPattern"): + # it's probably a lit frame finder + pass # we don't actually have anything to add for now + + class CompactCorrectionDeviceOverview(HorizontalLayout): def __init__(self, device_id, schema_hash): super().__init__(padding=0) @@ -933,6 +989,8 @@ def manager_device_overview( correction_device_schema, daq_device_ids, domain_device_ids, + arbiter_id, + arbiter_schema, ): mds_hash = schema_to_hash(manager_device_schema) cds_hash = schema_to_hash(correction_device_schema) @@ -965,20 +1023,35 @@ def manager_device_overview( ), ) + throttling = [] if "managedKeys.daqTrainStride" in mds_hash: - config_column.append( - titled("Data throttling")(boxed(VerticalLayout))( - EditableRow( - manager_device_id, - mds_hash, - "managedKeys.daqTrainStride", - 7, - 4, - ), - padding=0, - ), + throttling.append( + EditableRow( + manager_device_id, + mds_hash, + "managedKeys.daqTrainStride", + 7, + 4, + ) ) + if arbiter_id and arbiter_schema is None: + throttling.append( + StickerModel( + text=f"Failed to get schema {arbiter_id}; is the device up?", + width=8 * BASE_INC, + height=4 * BASE_INC, + ) + ) + elif arbiter_schema is not None: + throttling.append( + CompactArbiterOverview(arbiter_id, schema_to_hash(arbiter_schema)) + ) + + config_column.append( + titled("Data throttling")(boxed(VerticalLayout))(*throttling), + ) + top_row = [ ManagerDeviceStatus(manager_device_id), VerticalLayout(*config_column), @@ -1258,6 +1331,44 @@ def condition_checker_overview(device_id, schema): ) +@scene_generator +def simple_arbiter_overview(device_id, schema): + schema_hash = schema_to_hash(schema) + return VerticalLayout( + HorizontalLayout( + VerticalLayout( + MatcherSubclassStatus(device_id), + HorizontalLayout( + DisplayCommandModel( + keys=[f"{device_id}.start"], + width=5 * BASE_INC, + height=BASE_INC, + ), + DisplayCommandModel( + keys=[f"{device_id}.stop"], + width=5 * BASE_INC, + height=BASE_INC, + ), + ), + ), + ), + recursive_editable( + device_id, + schema_hash, + "frameSelection", + ), + PreviewDisplayArea( + device_id, + schema_hash, + "output", + image_key_name="data.dataFramePattern", + mask_key_name=None, + data_width=40 * BASE_INC, + data_height=14 * BASE_INC, + ), + ) + + @scene_generator def complex_arbiter_overview(device_id, schema): schema_hash = schema_to_hash(schema)