From 171e5b73351a9d26800dfbac9b32a19353c92808 Mon Sep 17 00:00:00 2001 From: Phantominumm Date: Tue, 18 Aug 2026 09:52:37 +0200 Subject: [PATCH 1/5] Register shutter object with BlissProxy before use - Call bliss_proxy.hardware.register(actuator_name) before get_object(), matching the other Bliss hardware objects --- mxcubecore/HardwareObjects/BlissShutter.py | 68 +++++++++++++++++----- 1 file changed, 52 insertions(+), 16 deletions(-) diff --git a/mxcubecore/HardwareObjects/BlissShutter.py b/mxcubecore/HardwareObjects/BlissShutter.py index ea12db60b5..821fc18686 100644 --- a/mxcubecore/HardwareObjects/BlissShutter.py +++ b/mxcubecore/HardwareObjects/BlissShutter.py @@ -31,17 +31,15 @@ actuator_name: safshut type: tango username: Safety shutter - objects: - controller: bliss.yaml """ +import logging from enum import ( Enum, unique, ) -import gevent - +from mxcubecore import HardwareRepository as HWR from mxcubecore.BaseHardwareObjects import HardwareObjectState from mxcubecore.HardwareObjects.abstract.AbstractShutter import AbstractShutter @@ -66,6 +64,9 @@ class BlissShutter(AbstractShutter): """BLISS implementation of AbstractShutter""" SPECIFIC_STATES = BlissShutterStates + # Only OPEN and CLOSED are user-commandable; the other values in VALUES + # (MOVING, DISABLE, STANDBY, FAULT) are read-only status indicators. + COMMANDABLE_VALUES = ("OPEN", "CLOSED") def __init__(self, name): super().__init__(name) @@ -75,9 +76,20 @@ def __init__(self, name): def init(self): """Initialise the predefined values""" - self.controller = self.get_object_by_role("controller") super().init() - self._bliss_obj = getattr(self.controller, self.actuator_name) + try: + bliss_proxy = HWR.beamline.bliss_proxy + bliss_proxy.hardware.register(self.actuator_name) + self._bliss_obj = bliss_proxy.get_object(self.actuator_name) + except Exception as exc: + logging.getLogger("MX3.HWR").warning( + "BlissShutter '%s': BLISS object '%s' not available (%s)", + self.actuator_name, + self.actuator_name, + exc, + ) + self._bliss_obj = None + return # for now we only treat tango type shutter self.shutter_type = self.get_property("type", "tango") try: @@ -88,14 +100,29 @@ def init(self): pass if self.shutter_type == "tango": self._initialise_values() - self._poll_task = gevent.spawn(self._poll_state) - self.update_state() + self._bliss_obj.subscribe("property", self._on_property_changed) + self._bliss_obj.subscribe("online", self._on_online_changed) + + self.update_state(self.get_state()) + self.update_value(self.get_value()) + + def _on_online_changed(self, online: bool) -> None: + """Callback for shutter online/offline events received via blissclient.""" + if not online: + self.update_state(HardwareObjectState.UNKNOWN) + else: + self._update_state() - def _poll_state(self): - while True: - self.update_value(self.get_value()) - gevent.sleep(0.5) + def _on_property_changed(self, data: dict) -> None: + """Callback for property changes received via blissclient.""" + if "state" in data: + self._update_state() + + def _update_state(self): + """Refresh state and value from the shutter object.""" + self.update_value(self.get_value()) + self.update_state(self.get_state()) def _initialise_values(self): """Add the tango states to VALUES""" @@ -116,21 +143,30 @@ def get_state(self): (enum 'HardwareObjectState'): Device state. """ try: - _state = self._bliss_obj.state.name + _state = self._bliss_obj.state or "UNKNOWN" + return self.SPECIFIC_STATES[_state].value[0] except (AttributeError, KeyError): return self.STATES.UNKNOWN - return self.SPECIFIC_STATES[_state].value[0] + except Exception: + return self.STATES.UNKNOWN def get_value(self): """Get the device value Returns: (Enum): Enum member, corresponding to the value or UNKNOWN. """ - # the return from BLISS value is an Enum - _val = self._bliss_obj.state.name + try: + _val = self._bliss_obj.state or "UNKNOWN" + except Exception: + _val = "UNKNOWN" return self.value_to_enum(_val) def _set_value(self, value): + if self._bliss_obj is None: + raise RuntimeError( + f"BlissShutter '{self.actuator_name}' is offline — " + "BLISS object not available" + ) if value.name == "OPEN": self._bliss_obj.open() elif value.name == "CLOSED": From a9642004facadf4230d37faf3d830b1744dea274 Mon Sep 17 00:00:00 2001 From: Antonia Beteva Date: Tue, 8 Sep 2026 17:17:17 +0200 Subject: [PATCH 2/5] Update mxcubecore/HardwareObjects/BlissShutter.py --- mxcubecore/HardwareObjects/BlissShutter.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/mxcubecore/HardwareObjects/BlissShutter.py b/mxcubecore/HardwareObjects/BlissShutter.py index 821fc18686..5951053e24 100644 --- a/mxcubecore/HardwareObjects/BlissShutter.py +++ b/mxcubecore/HardwareObjects/BlissShutter.py @@ -82,14 +82,8 @@ def init(self): bliss_proxy.hardware.register(self.actuator_name) self._bliss_obj = bliss_proxy.get_object(self.actuator_name) except Exception as exc: - logging.getLogger("MX3.HWR").warning( - "BlissShutter '%s': BLISS object '%s' not available (%s)", - self.actuator_name, - self.actuator_name, - exc, - ) - self._bliss_obj = None - return + msg = f"BlissShutter: BLISS object {self.actuator_name} not available {exc}" + logging.getLogger("MX3.HWR").warning(msg) # for now we only treat tango type shutter self.shutter_type = self.get_property("type", "tango") try: From 9907ee63d61e7e83698ae2265e15c1f460702fb9 Mon Sep 17 00:00:00 2001 From: Antonia Beteva Date: Tue, 8 Sep 2026 17:17:30 +0200 Subject: [PATCH 3/5] Update mxcubecore/HardwareObjects/BlissShutter.py --- mxcubecore/HardwareObjects/BlissShutter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mxcubecore/HardwareObjects/BlissShutter.py b/mxcubecore/HardwareObjects/BlissShutter.py index 5951053e24..be2f7d6cc0 100644 --- a/mxcubecore/HardwareObjects/BlissShutter.py +++ b/mxcubecore/HardwareObjects/BlissShutter.py @@ -98,8 +98,8 @@ def init(self): self._bliss_obj.subscribe("property", self._on_property_changed) self._bliss_obj.subscribe("online", self._on_online_changed) - self.update_state(self.get_state()) - self.update_value(self.get_value()) + self.update_state() + self.update_value() def _on_online_changed(self, online: bool) -> None: """Callback for shutter online/offline events received via blissclient.""" From e06c337525bf11c66b092a9bccea4b1c23d35faa Mon Sep 17 00:00:00 2001 From: Antonia Beteva Date: Tue, 8 Sep 2026 17:17:58 +0200 Subject: [PATCH 4/5] Update mxcubecore/HardwareObjects/BlissShutter.py --- mxcubecore/HardwareObjects/BlissShutter.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/mxcubecore/HardwareObjects/BlissShutter.py b/mxcubecore/HardwareObjects/BlissShutter.py index be2f7d6cc0..2d1d0836d1 100644 --- a/mxcubecore/HardwareObjects/BlissShutter.py +++ b/mxcubecore/HardwareObjects/BlissShutter.py @@ -64,9 +64,6 @@ class BlissShutter(AbstractShutter): """BLISS implementation of AbstractShutter""" SPECIFIC_STATES = BlissShutterStates - # Only OPEN and CLOSED are user-commandable; the other values in VALUES - # (MOVING, DISABLE, STANDBY, FAULT) are read-only status indicators. - COMMANDABLE_VALUES = ("OPEN", "CLOSED") def __init__(self, name): super().__init__(name) From cca66d4085826d8b84300698d4b721a91db762ae Mon Sep 17 00:00:00 2001 From: Antonia Beteva Date: Tue, 8 Sep 2026 17:18:20 +0200 Subject: [PATCH 5/5] Update mxcubecore/HardwareObjects/BlissShutter.py --- mxcubecore/HardwareObjects/BlissShutter.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/mxcubecore/HardwareObjects/BlissShutter.py b/mxcubecore/HardwareObjects/BlissShutter.py index 2d1d0836d1..95237adcff 100644 --- a/mxcubecore/HardwareObjects/BlissShutter.py +++ b/mxcubecore/HardwareObjects/BlissShutter.py @@ -108,12 +108,8 @@ def _on_online_changed(self, online: bool) -> None: def _on_property_changed(self, data: dict) -> None: """Callback for property changes received via blissclient.""" if "state" in data: - self._update_state() - - def _update_state(self): - """Refresh state and value from the shutter object.""" - self.update_value(self.get_value()) - self.update_state(self.get_state()) + self.update_state() + self.update_value() def _initialise_values(self): """Add the tango states to VALUES"""