pyaml-cs-oa 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. pyaml_cs_oa-0.1.0/.github/workflows/deploy-pypi.yaml +35 -0
  2. pyaml_cs_oa-0.1.0/.gitignore +2 -0
  3. pyaml_cs_oa-0.1.0/.pre-commit-config.yaml +21 -0
  4. pyaml_cs_oa-0.1.0/PKG-INFO +93 -0
  5. pyaml_cs_oa-0.1.0/README.md +62 -0
  6. pyaml_cs_oa-0.1.0/pyaml_cs_oa/__init__.py +79 -0
  7. pyaml_cs_oa-0.1.0/pyaml_cs_oa/container.py +170 -0
  8. pyaml_cs_oa-0.1.0/pyaml_cs_oa/controlsystem.py +154 -0
  9. pyaml_cs_oa-0.1.0/pyaml_cs_oa/epics.py +53 -0
  10. pyaml_cs_oa-0.1.0/pyaml_cs_oa/epicsR.py +13 -0
  11. pyaml_cs_oa-0.1.0/pyaml_cs_oa/epicsRW.py +13 -0
  12. pyaml_cs_oa-0.1.0/pyaml_cs_oa/epicsW.py +13 -0
  13. pyaml_cs_oa-0.1.0/pyaml_cs_oa/float_signal.py +61 -0
  14. pyaml_cs_oa-0.1.0/pyaml_cs_oa/scalar_aggregator.py +77 -0
  15. pyaml_cs_oa-0.1.0/pyaml_cs_oa/signal.py +88 -0
  16. pyaml_cs_oa-0.1.0/pyaml_cs_oa/tango.py +39 -0
  17. pyaml_cs_oa-0.1.0/pyaml_cs_oa/tangoR.py +13 -0
  18. pyaml_cs_oa-0.1.0/pyaml_cs_oa/tangoRW.py +13 -0
  19. pyaml_cs_oa-0.1.0/pyaml_cs_oa/types.py +35 -0
  20. pyaml_cs_oa-0.1.0/pyproject.toml +84 -0
  21. pyaml_cs_oa-0.1.0/tests/EBSTune-ophyd.yaml +1893 -0
  22. pyaml_cs_oa-0.1.0/tests/QD2_strength.csv +101 -0
  23. pyaml_cs_oa-0.1.0/tests/QF1_strength.csv +101 -0
  24. pyaml_cs_oa-0.1.0/tests/bessy2.mat +0 -0
  25. pyaml_cs_oa-0.1.0/tests/bessy2tune-KL.yaml +515 -0
  26. pyaml_cs_oa-0.1.0/tests/bessy2tune.yaml +519 -0
  27. pyaml_cs_oa-0.1.0/tests/convert_tokl.py +71 -0
  28. pyaml_cs_oa-0.1.0/tests/ebs.mat +0 -0
  29. pyaml_cs_oa-0.1.0/tests/test-tune-bessy.py +31 -0
  30. pyaml_cs_oa-0.1.0/tests/test-tune.py +25 -0
  31. pyaml_cs_oa-0.1.0/tests/tunemat-bessy.json +1 -0
  32. pyaml_cs_oa-0.1.0/tests/tunemat.json +1 -0
@@ -0,0 +1,35 @@
1
+ name: Publish Python Package
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - '[0-9]+.[0-9]+.[0-9]+'
7
+ workflow_dispatch:
8
+
9
+ jobs:
10
+ deploy:
11
+
12
+ runs-on: ubuntu-latest
13
+
14
+ #environment: release
15
+ permissions:
16
+ id-token: write
17
+
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+ - name: Set up Python
21
+ uses: actions/setup-python@v5
22
+ with:
23
+ python-version: '3.11'
24
+ cache: pip
25
+ cache-dependency-path: '**/pyproject.toml'
26
+ - name: Install dependencies
27
+ run: |
28
+ python -m pip install --upgrade pip
29
+ pip install hatch
30
+ - name: Build package
31
+ run: hatch build
32
+ #- name: Test package
33
+ # run: hatch run test
34
+ - name: Publish
35
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,2 @@
1
+ __pycache__/
2
+ *.egg-info/
@@ -0,0 +1,21 @@
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: v6.0.0
4
+ hooks:
5
+ - id: trailing-whitespace
6
+ - id: end-of-file-fixer
7
+
8
+ - repo: https://github.com/astral-sh/ruff-pre-commit
9
+ rev: v0.14.5
10
+ hooks:
11
+ - id: ruff
12
+ args: [--fix]
13
+ - id: ruff-format
14
+
15
+ - repo: https://github.com/pre-commit/mirrors-mypy
16
+ rev: v1.18.2
17
+ hooks:
18
+ - id: mypy
19
+ additional_dependencies: [
20
+ "pydantic>=2.0",
21
+ ]
@@ -0,0 +1,93 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyaml-cs-oa
3
+ Version: 0.1.0
4
+ Summary: PyAML control system plugin for ophyd-async
5
+ Maintainer-email: Yoshiteru Hidaka <yhidaka@bnl.gov>
6
+ Keywords: Accelerator,Commissioning,Digital Twin,EPICS,Operation,Synchrotron,Tango,Tuning
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Intended Audience :: Science/Research
9
+ Classifier: Natural Language :: English
10
+ Classifier: Programming Language :: Python
11
+ Classifier: Programming Language :: Python :: 3 :: Only
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Topic :: Scientific/Engineering :: Physics
15
+ Requires-Python: >=3.11
16
+ Requires-Dist: ophyd-async
17
+ Requires-Dist: pyaml
18
+ Requires-Dist: pydantic>=2.0
19
+ Provides-Extra: dev
20
+ Requires-Dist: mypy; extra == 'dev'
21
+ Requires-Dist: ophyd-async[ca,pva]; extra == 'dev'
22
+ Requires-Dist: ophyd-async[tango]; extra == 'dev'
23
+ Requires-Dist: pre-commit; extra == 'dev'
24
+ Requires-Dist: pytest; extra == 'dev'
25
+ Requires-Dist: ruff; extra == 'dev'
26
+ Provides-Extra: epics
27
+ Requires-Dist: ophyd-async[ca,pva]; extra == 'epics'
28
+ Provides-Extra: tango
29
+ Requires-Dist: ophyd-async[tango]; extra == 'tango'
30
+ Description-Content-Type: text/markdown
31
+
32
+ # `pyaml-cs-oa`
33
+
34
+ **PyAML control system plugin for ophyd-async**
35
+
36
+ `pyaml-cs-oa` is a plugin for `PyAML` based on `ophyd-async`, which
37
+ currently supports EPICS and Tango control systems.
38
+
39
+ ---
40
+
41
+ ## 🔧 Installation
42
+
43
+ ### **Requirements**
44
+
45
+ - Python **3.11+**
46
+
47
+ - Depending on your runtime environment, you may want to install support for EPICS or Tango.
48
+
49
+ ### **EPICS CA/PVA Support**
50
+
51
+ ```
52
+ pip install pyaml-cs-oa[epics]
53
+ ```
54
+
55
+ This installs:
56
+
57
+ - `ophyd-async[ca,pva]`
58
+
59
+ ### **Tango Support**
60
+
61
+ ```
62
+ pip install pyaml-cs-oa[tango]
63
+ ```
64
+
65
+ This installs:
66
+
67
+ - `ophyd-async[tango]`
68
+
69
+ ---
70
+
71
+ ## 🧪 Developer Installation
72
+
73
+ If you are contributing, debugging, or running the test suite (no test
74
+ currently provided):
75
+
76
+ ```
77
+ pip install pyaml-cs-oa[dev]
78
+ ```
79
+
80
+ This installs:
81
+
82
+ - `ophyd-async[ca,pva]`
83
+ - `ophyd-async[tango]`
84
+ - `pre-commit`
85
+ - `ruff`
86
+ - `mypy`
87
+ - `pytest`
88
+
89
+ ### Setup pre-commit hooks
90
+
91
+ ```
92
+ pre-commit install
93
+ ```
@@ -0,0 +1,62 @@
1
+ # `pyaml-cs-oa`
2
+
3
+ **PyAML control system plugin for ophyd-async**
4
+
5
+ `pyaml-cs-oa` is a plugin for `PyAML` based on `ophyd-async`, which
6
+ currently supports EPICS and Tango control systems.
7
+
8
+ ---
9
+
10
+ ## 🔧 Installation
11
+
12
+ ### **Requirements**
13
+
14
+ - Python **3.11+**
15
+
16
+ - Depending on your runtime environment, you may want to install support for EPICS or Tango.
17
+
18
+ ### **EPICS CA/PVA Support**
19
+
20
+ ```
21
+ pip install pyaml-cs-oa[epics]
22
+ ```
23
+
24
+ This installs:
25
+
26
+ - `ophyd-async[ca,pva]`
27
+
28
+ ### **Tango Support**
29
+
30
+ ```
31
+ pip install pyaml-cs-oa[tango]
32
+ ```
33
+
34
+ This installs:
35
+
36
+ - `ophyd-async[tango]`
37
+
38
+ ---
39
+
40
+ ## 🧪 Developer Installation
41
+
42
+ If you are contributing, debugging, or running the test suite (no test
43
+ currently provided):
44
+
45
+ ```
46
+ pip install pyaml-cs-oa[dev]
47
+ ```
48
+
49
+ This installs:
50
+
51
+ - `ophyd-async[ca,pva]`
52
+ - `ophyd-async[tango]`
53
+ - `pre-commit`
54
+ - `ruff`
55
+ - `mypy`
56
+ - `pytest`
57
+
58
+ ### Setup pre-commit hooks
59
+
60
+ ```
61
+ pre-commit install
62
+ ```
@@ -0,0 +1,79 @@
1
+ import asyncio
2
+ import contextlib
3
+ from typing import Awaitable, Any
4
+ import atexit
5
+
6
+ __version__ = "0.1.0"
7
+
8
+ # One persistent event loop
9
+ _loop = None
10
+ _nest_asyncio_applied = False
11
+
12
+ def loop() -> asyncio.AbstractEventLoop:
13
+
14
+ global _loop, _nest_asyncio_applied
15
+
16
+ # Try to get the currently running loop (e.g., in Jupyter)
17
+ try:
18
+ running_loop = asyncio.get_running_loop()
19
+ # We found a running loop (Jupyter case)
20
+ if not _nest_asyncio_applied:
21
+ try:
22
+ import nest_asyncio
23
+ nest_asyncio.apply(running_loop)
24
+ _nest_asyncio_applied = True
25
+ except ImportError:
26
+ pass
27
+ return running_loop
28
+ except RuntimeError:
29
+ # No running loop, create our own
30
+ pass
31
+
32
+ if _loop is None or _loop.is_closed():
33
+ _loop = asyncio.new_event_loop()
34
+ asyncio.set_event_loop(_loop)
35
+
36
+ # Apply nest_asyncio to our new loop
37
+ if not _nest_asyncio_applied:
38
+ try:
39
+ import nest_asyncio
40
+ nest_asyncio.apply(_loop)
41
+ _nest_asyncio_applied = True
42
+ except ImportError:
43
+ pass
44
+
45
+ return _loop
46
+ loop() # Make sure to initialize `_loop`
47
+
48
+ def _reap_done_tasks(evloop: asyncio.AbstractEventLoop) -> None:
49
+ """Reap exceptions from tasks that are already DONE on this loop.
50
+ Does not cancel or otherwise touch pending tasks.
51
+ """
52
+ if evloop is None or evloop.is_closed():
53
+ return
54
+
55
+ try:
56
+ # Snapshot; tasks may change during iteration
57
+ for t in tuple(asyncio.all_tasks(evloop)):
58
+ if not t.done():
59
+ continue
60
+ # If the task is cancelled, .result() raises CancelledError — suppress that.
61
+ # If the task failed, .result() raises its exception — suppress to just mark it retrieved.
62
+ with contextlib.suppress(asyncio.CancelledError, Exception):
63
+ t.result()
64
+ except (RuntimeError, AttributeError):
65
+ # During shutdown, asyncio may be partially cleaned up
66
+ pass
67
+
68
+
69
+ def arun(coro: Awaitable[Any]) -> Any:
70
+
71
+ evloop = loop()
72
+
73
+ try:
74
+ return evloop.run_until_complete(coro)
75
+ finally:
76
+ # Clean up completed/cancelled tasks so residual CancelledError
77
+ # doesn't leak to next run
78
+ _reap_done_tasks(evloop)
79
+
@@ -0,0 +1,170 @@
1
+ import asyncio
2
+ import inspect
3
+ from collections.abc import Awaitable, Callable
4
+ from typing import TypeVar
5
+ from .signal import OASignal
6
+
7
+ from ophyd_async.core import (
8
+ SignalDatatypeT,
9
+ SignalR,
10
+ SignalW,
11
+ set_and_wait_for_other_value,
12
+ )
13
+
14
+ T = TypeVar("T")
15
+
16
+ from . import arun
17
+
18
+ def _looks_disconnected(exc: BaseException) -> bool:
19
+ # Keep it generic: ophyd-async wraps cancellations in TimeoutError;
20
+ # tango/epics transports often raise CancelledError or "NotConnected" types.
21
+ return isinstance(exc, (asyncio.CancelledError, TimeoutError))
22
+
23
+
24
+ async def _recover_once(
25
+ run: Callable[[], Awaitable[T]],
26
+ reconnect: Callable[[], Awaitable[None]],
27
+ peer: OASignal,
28
+ ) -> T:
29
+ try:
30
+ return await run()
31
+ except BaseException as exc:
32
+ if not _looks_disconnected(exc):
33
+ raise
34
+ # Attempt reconnect of the same Signal first
35
+ try:
36
+ await reconnect()
37
+ return await run()
38
+ except BaseException:
39
+ # If that fails and we have a way to rebuild, do so and try one more time
40
+ if peer is not None:
41
+ maybe_awaitable = peer.build()
42
+ if inspect.isawaitable(maybe_awaitable):
43
+ await maybe_awaitable
44
+ await reconnect()
45
+ return await run()
46
+ raise
47
+
48
+
49
+ class OAReadback():
50
+ """A readback object."""
51
+
52
+ def __init__(self, r_signal: SignalR[SignalDatatypeT]):
53
+ self._r_sig = r_signal
54
+
55
+ async def _run_get(self) -> SignalDatatypeT:
56
+ await self._r_sig.connect()
57
+ backend = self._r_sig._connector.backend
58
+ return await backend.get_value()
59
+
60
+ async def async_get(self) -> SignalDatatypeT:
61
+ return await _recover_once(
62
+ self._run_get,
63
+ self._r_sig.connect,
64
+ getattr(self._r_sig, "__peer__", None),
65
+ )
66
+
67
+ async def _run_read(self) -> SignalDatatypeT:
68
+ await self._r_sig.connect()
69
+ backend = self._r_sig._connector.backend
70
+ return await backend.get_reading()
71
+
72
+ async def async_read(self) -> SignalDatatypeT:
73
+ return await _recover_once(
74
+ self._run_read,
75
+ self._r_sig.connect,
76
+ getattr(self._r_sig, "__peer__", None),
77
+ )
78
+
79
+ def get(self) -> SignalDatatypeT:
80
+ """Synchronous wrapper around `async_get()`."""
81
+ return arun(self.async_get())
82
+
83
+ class OASetpoint():
84
+ def __init__(
85
+ self,
86
+ w_signal: SignalW[SignalDatatypeT],
87
+ r_signal: SignalR[SignalDatatypeT] | None = None,
88
+ ):
89
+ self._w_sig = w_signal
90
+ self._r_sig = r_signal # used only for `set_and_wait()`
91
+ self._has_r_sig = (r_signal is not None)
92
+
93
+ async def _run_get(self) -> SignalDatatypeT:
94
+ await self._w_sig.connect()
95
+ backend = self._w_sig._connector.backend
96
+ return await backend.get_setpoint()
97
+
98
+ async def async_get(self) -> SignalDatatypeT:
99
+ return await _recover_once(
100
+ self._run_get,
101
+ self._w_sig.connect,
102
+ getattr(self._w_sig, "__peer__", None),
103
+ )
104
+
105
+ async def _run_read(self) -> SignalDatatypeT:
106
+ await self._w_sig.connect()
107
+ backend = self._w_sig._connector.backend
108
+ return await backend.get_reading()
109
+
110
+ async def async_read(self) -> SignalDatatypeT:
111
+ return await _recover_once(
112
+ self._run_read,
113
+ self._w_sig.connect,
114
+ getattr(self._w_sig, "__peer__", None),
115
+ )
116
+
117
+ async def _run_set(self, value):
118
+ await self._w_sig.connect()
119
+ status = self._w_sig.set(value)
120
+ return status
121
+
122
+ async def async_set(self, value):
123
+ return await _recover_once(
124
+ lambda: self._run_set(value),
125
+ self._w_sig.connect,
126
+ getattr(self._w_sig, "__peer__", None),
127
+ )
128
+
129
+ async def _reconnect_both(self) -> None:
130
+ await asyncio.gather(self._w_sig.connect(), self._r_sig.connect())
131
+
132
+ async def _rebuild_both(self) -> None:
133
+ w_rebuild = getattr(self._w_sig, "__peer__", None)
134
+ r_rebuild = getattr(self._r_sig, "__peer__", None)
135
+ if w_rebuild is not None:
136
+ w_rebuild()
137
+ if r_rebuild is not None:
138
+ r_rebuild()
139
+
140
+ async def _run_set_and_wait(self, value) -> None:
141
+ if not self._has_r_sig:
142
+ raise RuntimeError(
143
+ "Cannot use set_and_wait() without a matching readback signal."
144
+ )
145
+ await self._reconnect_both()
146
+ await set_and_wait_for_other_value(self._w_sig, value, self._r_sig, value)
147
+
148
+ async def async_set_and_wait(self, value) -> None:
149
+ return await _recover_once(
150
+ lambda: self._run_set_and_wait(value),
151
+ self._reconnect_both,
152
+ self._rebuild_both,
153
+ )
154
+
155
+ async def _complete_set(self, value):
156
+ status = await self.async_set(value)
157
+ await status # Wait for completion before returning
158
+ return status
159
+
160
+ def set(self, value):
161
+ """Synchronous wrapper around `async_set()`."""
162
+ return arun(self._complete_set(value))
163
+
164
+ def get(self) -> SignalDatatypeT:
165
+ """Synchronous wrapper around `async_get()`."""
166
+ return arun(self.async_get())
167
+
168
+ def set_and_wait(self, value) -> None:
169
+ """Synchronous wrapper around `async_set_and_wait()`."""
170
+ return arun(self.async_set_and_wait(value))
@@ -0,0 +1,154 @@
1
+ import os
2
+ import logging
3
+ import copy
4
+
5
+ from pyaml.control.controlsystem import ControlSystem
6
+ from pydantic import BaseModel
7
+ from pyaml.common.exception import PyAMLException
8
+
9
+ PYAMLCLASS : str = "OphydAsyncControlSystem"
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+ from .types import (
14
+ EpicsConfigR,
15
+ EpicsConfigW,
16
+ EpicsConfigRW,
17
+ TangoConfigR,
18
+ TangoConfigRW,
19
+ )
20
+ from .signal import OASignal
21
+ from .epicsR import EpicsR
22
+ from .epicsW import EpicsW
23
+ from .epicsRW import EpicsRW
24
+ from .tangoR import TangoR
25
+ from .tangoRW import TangoRW
26
+
27
+ class ConfigModel(BaseModel):
28
+
29
+ """
30
+ Configuration model for an OA Control System.
31
+
32
+ Attributes
33
+ ----------
34
+ name : str
35
+ Name of the control system.
36
+ prefix : str
37
+ Prefix added to the PV or attribute name. It can be a
38
+ for instance, TANGO_HOST, or a PV prefix.
39
+ debug_level : int
40
+ Debug verbosity level.
41
+ scalar_aggregator : str
42
+ Aggregator module for scalar values. If none specified, writings and
43
+ readings of sclar value are serialized.
44
+ vector_aggregator : str
45
+ Aggregator module for vecrors. If none specified, writings and readings
46
+ of vector are serialized,
47
+ """
48
+
49
+ name: str
50
+ prefix: str = ""
51
+ debug_level: str=None
52
+ scalar_aggregator: str | None = "pyaml_cs_oa.scalar_aggregator"
53
+ vector_aggregator: str | None = None
54
+
55
+
56
+ class OphydAsyncControlSystem(ControlSystem):
57
+ """A generic control system using ophyd_async backend."""
58
+
59
+ def __init__(self, cfg: ConfigModel):
60
+ super().__init__()
61
+ self._cfg = cfg
62
+ self._devices = {} # Dict containing all attached DeviceAccess
63
+
64
+ if self._cfg.debug_level:
65
+ log_level = getattr(logging, self._cfg.debug_level, logging.WARNING)
66
+ logger.parent.setLevel(log_level)
67
+ logger.setLevel(log_level)
68
+
69
+ logger.log(logging.WARNING, f"OA control system binding for PyAML initialized with name '{self._cfg.name}'"
70
+ f" and prefix='{self._cfg.prefix}'")
71
+
72
+ def attach(self, devs: list[OASignal]) -> list[OASignal]:
73
+ return self._attach(devs,False)
74
+
75
+ def attach_array(self, devs: list[OASignal]) -> list[OASignal]:
76
+ return self._attach(devs,True)
77
+
78
+ def _attach(self, devs: list[OASignal],is_array:bool) -> list[OASignal]:
79
+ # Concatenate the prefix
80
+ newDevs = []
81
+ for d in devs:
82
+ if d is not None:
83
+
84
+ sig_cfg = d._cfg
85
+ sig_cfg_cls = sig_cfg.__class__
86
+
87
+ if isinstance(d._cfg,EpicsConfigR):
88
+ key = self._cfg.prefix + d._cfg.read_pvname
89
+ sig_cls = EpicsR
90
+ config = dict(read_pvname=key,timeout_ms=d._cfg.timeout_ms)
91
+ elif isinstance(d._cfg,EpicsConfigW):
92
+ key = self._cfg.prefix + d._cfg.write_pvname
93
+ sig_cls = EpicsW
94
+ config = dict(write_pvname=key,timeout_ms=d._cfg.timeout_ms)
95
+ elif isinstance(d._cfg,EpicsConfigRW):
96
+ key = self._cfg.prefix + d._cfg.read_pvname + d._cfg.write_pvname
97
+ sig_cls = EpicsRW
98
+ config = dict(read_pvname=self._cfg.prefix + d._cfg.read_pvname, write_pvname=self._cfg.prefix + d._cfg.write_pvname,timeout_ms=d._cfg.timeout_ms)
99
+ elif isinstance(d._cfg,TangoConfigR):
100
+ key = self._cfg.prefix + d._cfg.attribute
101
+ sig_cls = TangoR
102
+ config = dict(attribute=key,timeout_ms=d._cfg.timeout_ms)
103
+ elif isinstance(d._cfg,TangoConfigRW):
104
+ key = self._cfg.prefix + d._cfg.attribute
105
+ sig_cls = TangoRW
106
+ config = dict(attribute=key,timeout_ms=d._cfg.timeout_ms)
107
+ else:
108
+ raise PyAMLException(f"OphydAsyncControlSystem: Unsupported type {type(sig_cfg)}")
109
+
110
+ if key not in self._devices:
111
+ nr = sig_cls(sig_cfg_cls(**config),is_array)
112
+ nr.build()
113
+ self._devices[key] = nr
114
+
115
+ newDevs.append(self._devices[key])
116
+ else:
117
+ newDevs.append(None)
118
+ return newDevs
119
+
120
+ def name(self) -> str:
121
+ """
122
+ Return the name of the control system.
123
+
124
+ Returns
125
+ -------
126
+ str
127
+ Name of the control system.
128
+ """
129
+ return self._cfg.name
130
+
131
+ def scalar_aggregator(self) -> str | None:
132
+ """
133
+ Returns the module name used for handling aggregator of DeviceAccess
134
+
135
+ Returns
136
+ -------
137
+ str
138
+ Aggregator module name
139
+ """
140
+ return self._cfg.scalar_aggregator
141
+
142
+ def vector_aggregator(self) -> str | None:
143
+ """
144
+ Returns the module name used for handling aggregator of DeviceVectorAccess
145
+
146
+ Returns
147
+ -------
148
+ str
149
+ Aggregator module name
150
+ """
151
+ return self._cfg.vector_aggregator
152
+
153
+ def __repr__(self):
154
+ return repr(self._cfg).replace("ConfigModel",self.__class__.__name__)
@@ -0,0 +1,53 @@
1
+ from ophyd_async.epics.signal import epics_signal_r, epics_signal_w, epics_signal_rw
2
+ from ophyd_async.core import Array1D
3
+ import numpy
4
+
5
+ from .container import OAReadback as Readback
6
+ from .container import OASetpoint as Setpoint
7
+ from .types import (
8
+ ControlSysConfig,
9
+ EpicsConfigR,
10
+ EpicsConfigRW,
11
+ EpicsConfigW,
12
+ )
13
+
14
+
15
+ def get_SP_RB(cfg: ControlSysConfig,is_array:bool) -> tuple[Setpoint | None, Readback | None]:
16
+ setpoint: Setpoint | None = None
17
+ readback: Readback | None = None
18
+
19
+ assert isinstance(cfg, (EpicsConfigRW, EpicsConfigR, EpicsConfigW))
20
+
21
+ if isinstance(cfg, EpicsConfigR):
22
+ r_sig = epics_signal_r(
23
+ datatype=float if not is_array else Array1D[numpy.float64],
24
+ read_pv=cfg.read_pvname,
25
+ name="",
26
+ timeout = cfg.timeout_ms / 1000.,
27
+ )
28
+ readback = Readback(r_sig)
29
+ setpoint = None
30
+
31
+ if isinstance(cfg, EpicsConfigW):
32
+ w_sig = epics_signal_w(
33
+ datatype=float if not is_array else Array1D[numpy.float64],
34
+ write_pv=cfg.write_pvname,
35
+ name="",
36
+ timeout = cfg.timeout_ms / 1000.,
37
+ )
38
+ readback = None
39
+ setpoint = Setpoint(w_sig)
40
+
41
+ if isinstance(cfg, EpicsConfigRW):
42
+ w_sig = epics_signal_rw(
43
+ datatype=float if not is_array else Array1D[numpy.float64],
44
+ read_pv=cfg.read_pvname,
45
+ write_pv=cfg.write_pvname,
46
+ name="",
47
+ timeout = cfg.timeout_ms / 1000.,
48
+ )
49
+ readback = Readback(w_sig)
50
+ setpoint = Setpoint(w_sig)
51
+
52
+
53
+ return setpoint, readback
@@ -0,0 +1,13 @@
1
+ from .float_signal import FloatSignalContainer
2
+ from .types import EpicsConfigR
3
+
4
+ PYAMLCLASS : str = "EpicsR"
5
+
6
+ class ConfigModel(EpicsConfigR):
7
+ unit: str = ""
8
+
9
+ class EpicsR(FloatSignalContainer):
10
+ def __init__(self, cfg: ConfigModel, is_array=False):
11
+ super().__init__(cfg,is_array)
12
+ def get_cs(self) -> str:
13
+ return "epics"
@@ -0,0 +1,13 @@
1
+ from .float_signal import FloatSignalContainer
2
+ from .types import EpicsConfigRW
3
+
4
+ PYAMLCLASS : str = "EpicsRW"
5
+
6
+ class ConfigModel(EpicsConfigRW):
7
+ unit: str = ""
8
+
9
+ class EpicsRW(FloatSignalContainer):
10
+ def __init__(self, cfg: ConfigModel, is_array=False):
11
+ super().__init__(cfg,is_array)
12
+ def get_cs(self) -> str:
13
+ return "epics"