diff --git a/tango/pyaml/attribute.py b/tango/pyaml/attribute.py index 587f5f6..9452cf8 100644 --- a/tango/pyaml/attribute.py +++ b/tango/pyaml/attribute.py @@ -44,21 +44,14 @@ class Attribute(DeviceAccess, InitializableElement): def __init__(self, cfg: ConfigModel, writable=True): super().__init__() self._cfg = cfg - - self._attribute_dev_name, self._attr_name = cfg.attribute.rsplit("/", 1) - self._unit = cfg.unit self._writable = writable self._attribute_dev:tango.DeviceProxy = None - if TangoControlSystem.is_initialized(): - self.initialize() - else: - TangoControlSystem.get_instance().add_initializable(self) - - def initialize(self): super().initialize() try: + self._attribute_dev_name, self._attr_name = self._cfg.attribute.rsplit("/", 1) + self._unit = self._cfg.unit self._attribute_dev = DeviceFactory().get_device(self._attribute_dev_name) except tango.DevFailed as df: raise tango_to_PyAMLException(df) diff --git a/tango/pyaml/attribute_list.py b/tango/pyaml/attribute_list.py index 762dda6..7c38142 100644 --- a/tango/pyaml/attribute_list.py +++ b/tango/pyaml/attribute_list.py @@ -7,7 +7,6 @@ from pyaml.control.readback_value import Value, Quality import tango -from .controlsystem import TangoControlSystem from .initializable_element import InitializableElement PYAMLCLASS: str = "AttributeList" @@ -56,11 +55,6 @@ def __init__(self, cfg: ConfigModel): if attribute_dev_name not in self._attr_dev[attr_name]: self._attr_dev[attr_name].append(attribute_dev_name) - if TangoControlSystem.is_initialized(): - self.initialize() - else: - TangoControlSystem.get_instance().add_initializable(self) - def initialize(self): super().initialize() for attr_name, dev_list in self._attr_dev.items(): diff --git a/tango/pyaml/controlsystem.py b/tango/pyaml/controlsystem.py index 60683b9..ede0151 100644 --- a/tango/pyaml/controlsystem.py +++ b/tango/pyaml/controlsystem.py @@ -1,9 +1,10 @@ import os import logging -from threading import Lock +import copy from pydantic import BaseModel, field_validator from pyaml.control.controlsystem import ControlSystem +from pyaml.control.deviceaccess import DeviceAccess from .device_factory import DeviceFactory PYAMLCLASS : str = "TangoControlSystem" @@ -11,7 +12,6 @@ logger = logging.getLogger(__name__) - class ConfigModel(BaseModel): """ Configuration model for a Tango Control System. @@ -21,11 +21,9 @@ class ConfigModel(BaseModel): name : str Name of the control system. tango_host : str - Tango host URL. + Tango host URL. Default is the TANGO_HOST variable. debug_level : int Debug verbosity level. - lazy_devices: bool - Construct DeviceProxy when needed scalar_aggregator : str Aggregator module for scalar values. If none specified, writings and readings of sclar value are serialized. vector_aggregator : str @@ -34,7 +32,7 @@ class ConfigModel(BaseModel): Device timeout in milli seconds. """ name: str - tango_host: str + tango_host: str | None = None debug_level: str=None lazy_devices: bool = True scalar_aggregator: str | None = "tango.pyaml.multi_attribute" @@ -50,39 +48,48 @@ class TangoControlSystem(ControlSystem): cfg : ConfigModel Configuration parameters including name, host and debug level. """ - _instance = None - _lock = Lock() - - def __new__(cls, cfg: ConfigModel): - """ - No matter how many times you call PyAMLFactory(), it will be created only once. - """ - with cls._lock: - if cls._instance is None: - cls._instance = super().__new__(cls) - cls._instance._cfg = cfg - cls._instance._initializable_elements = [] - cls._instance._initialized = False - cls._instance._lazy_devices = cfg.lazy_devices - DeviceFactory().set_timeout_ms(cfg.timeout_ms) - return cls._instance - - @classmethod - def is_initialized(cls): - return cls._instance is not None and cls._instance.is_instance_initialized() - - - @classmethod - def get_instance(cls) -> "TangoControlSystem": - return cls._instance def __init__(self, cfg: ConfigModel): super().__init__() self._cfg = cfg + self.__devices = {} # Dict containing all attached DeviceAccess - def is_instance_initialized(self) -> bool: - return self._initialized + if self._cfg.debug_level: + log_level = getattr(logging, self._cfg.debug_level, logging.WARNING) + logger.parent.setLevel(log_level) + logger.setLevel(log_level) + + logger.log(logging.WARNING, f"Tango control system binding for PyAML initialized with name '{self._cfg.name}'" + f" and TANGO_HOST={self._cfg.tango_host}") + + def __newref(self,obj,new_name:str): + # Shallow copy the object + newObj = copy.copy(obj) + # Shallow copy the config object + # to allow a new attribute name + newObj._cfg = copy.copy(obj._cfg) + newObj._cfg.attribute = new_name + return newObj + + def attach(self, devs: list[DeviceAccess]) -> list[DeviceAccess]: + # Concatenate the tango_host prefix + newDevs = [] + for d in devs: + if d is not None: + if self._cfg.tango_host: + full_name = "//" + self._cfg.tango_host + "/" + d._cfg.attribute + else: + full_name = d._cfg.attribute + if full_name not in self.__devices: + self.__devices[full_name] = self.__newref(d,full_name) + newDevs.append(self.__devices[full_name]) + else: + newDevs.append(None) + return newDevs + @classmethod + def is_initialized(cls): + return cls._instance is not None and cls._instance.is_instance_initialized() def name(self) -> str: """ @@ -117,65 +124,5 @@ def vector_aggregator(self) -> str | None: """ return self._cfg.vector_aggregator - - def init_cs(self): - """ - Initialize the control system. - - This method is a placeholder and should be implemented as needed. - """ - if self._initialized: - logger.log(logging.WARNING, f"Tango control system binding for PyAML was already initialized" - f" with name '{self._cfg.name}' and TANGO_HOST={os.environ['TANGO_HOST']}") - return - - if self._cfg.tango_host: - os.environ["TANGO_HOST"] = self._cfg.tango_host - if self._cfg.debug_level: - log_level = getattr(logging, self._cfg.debug_level, logging.WARNING) - logger.parent.setLevel(log_level) - logger.setLevel(log_level) - - """ - Eagerly initializes registered elements only if *lazy_devices* is False. - When *lazy_devices* is True (default), initialization is deferred until - `warmup()` or first-use via `ensure_initialized()`. - """ - if not self._cfg.lazy_devices: - self._do_initialize_all() - else: - logger.debug("init_cs(): lazy mode enabled; deferring initialization") - - self._initialized = True - - logger.log(logging.INFO, f"Tango control system binding for PyAML initialized with name '{self._cfg.name}'" - f" and TANGO_HOST={os.environ['TANGO_HOST']}") - - - def _do_initialize_all(self) -> None: - # Initialize all registered elements exactly once - for elem in self._initializable_elements: - try: - elem.initialize() - except Exception as exc: # pragma: no cover - logger.exception("Failed to initialize %r: %s", elem, exc) - raise - self._initializable_elements.clear() - - - def add_initializable(self, initializable): - self._initializable_elements.append(initializable) - - - def warmup(self) -> None: - """Explicitly switch to eager behavior and initialize now.""" - with self._lock: - self._lazy_devices = False - self._do_initialize_all() - - @property - def lazy_devices(self) -> bool: - return self._lazy_devices - def __repr__(self): return repr(self._cfg).replace("ConfigModel",self.__class__.__name__) \ No newline at end of file diff --git a/tango/pyaml/initializable_element.py b/tango/pyaml/initializable_element.py index ff2eaa2..e6f3969 100644 --- a/tango/pyaml/initializable_element.py +++ b/tango/pyaml/initializable_element.py @@ -2,9 +2,6 @@ import pyaml -from .controlsystem import TangoControlSystem - - class InitializableElement(metaclass=ABCMeta): def __init__(self): @@ -23,7 +20,5 @@ def is_initialized(self) -> bool: def _ensure_initialized(self): if not self.is_initialized(): - if not TangoControlSystem.get_instance().lazy_devices: - raise pyaml.PyAMLException(f"The attribute {self.name()} is not initialized.") self.initialize() pass