Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 2 additions & 9 deletions tango/pyaml/attribute.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 0 additions & 6 deletions tango/pyaml/attribute_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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():
Expand Down
133 changes: 40 additions & 93 deletions tango/pyaml/controlsystem.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
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"

logger = logging.getLogger(__name__)



class ConfigModel(BaseModel):
"""
Configuration model for a Tango Control System.
Expand All @@ -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
Expand All @@ -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"
Expand All @@ -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:
"""
Expand Down Expand Up @@ -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__)
5 changes: 0 additions & 5 deletions tango/pyaml/initializable_element.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@

import pyaml

from .controlsystem import TangoControlSystem


class InitializableElement(metaclass=ABCMeta):

def __init__(self):
Expand All @@ -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