Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tango support #437

Merged
merged 156 commits into from
Oct 8, 2024
Merged
Show file tree
Hide file tree
Changes from 137 commits
Commits
Show all changes
156 commits
Select commit Hold shift + click to select a range
1b18cd1
first try. Made signals pv for attributes
Oct 16, 2023
957defe
rebasing
burkeds Feb 26, 2024
e0e3b3c
signals for attributes and commands and signal_test
Oct 19, 2023
94b5c37
test for generic TangoDevice
Oct 19, 2023
3b6547e
rebasing
burkeds Feb 26, 2024
4874c44
added "TangoSignalRW", "TangoSignalW", "TangoSignalBackend" - they ha…
Oct 20, 2023
969ff1f
rebasing
burkeds Feb 26, 2024
26ccd04
Sardana motor working
Nov 6, 2023
cf2decb
added OmsVME58, DGG2, SIS3820 devices and tests
Jan 15, 2024
5506c6b
added test to examples
Jan 15, 2024
1529376
dgg2 -> Preparable, added set time to example
Jan 16, 2024
1caf5ab
Renamed test to avoid duplicate names
burkeds Feb 26, 2024
fc94a5d
Added timeout to TangoTransport.connect() signature. All pytests pass
burkeds Feb 26, 2024
fc0c0f5
Added pytango to dependencies
burkeds Feb 27, 2024
64db40a
Bringing code to linting compliance with black, flake8 and isort.
burkeds Feb 27, 2024
5719489
reversed some changes which caused mypy errors
burkeds Feb 27, 2024
d783ac2
Fixed typos in some definitions.
burkeds Feb 27, 2024
9291f6d
Merge branch 'main' into development
burkeds Mar 14, 2024
e978d75
Changed signal names and added _stop method expected by the queueserv…
burkeds Apr 15, 2024
ebc3297
Reversed last commit
burkeds Apr 15, 2024
a59524f
merging
burkeds Apr 15, 2024
d1fbcba
Added calls to set_name in register_signals so that the names of chil…
burkeds Apr 18, 2024
93ed5aa
Added polling of position to OmsVME58Motor every 0.5 seconds.
burkeds Apr 23, 2024
1d8ed40
merged
burkeds Apr 29, 2024
7e87bc7
Added source method to TangoSignal and _backend which has the @proper…
burkeds Apr 30, 2024
b325684
DeviceProxy is now being awaited in connect so that Devices do not ha…
burkeds Apr 30, 2024
906409c
changed _read_signals and _config_signals to _readables and _configur…
burkeds May 2, 2024
0df5b0f
Made get_descriptor signature compliant with changes to SignalBacken.
burkeds May 2, 2024
9889272
Created new 'TangoMover' class. A generic locatable and stoppable dev…
burkeds May 3, 2024
ecc6111
Tuning sample device definitions
burkeds May 23, 2024
6729c43
Added some typing. Fixed stop signature for OmsVMEMotor
burkeds May 24, 2024
a084558
Example devices can now take an options sources keyword. This is a di…
burkeds May 28, 2024
fc88243
get_dtype can now handle strings
burkeds May 29, 2024
b002b14
.
burkeds May 30, 2024
febd81f
Reversed change to core util.
burkeds Jun 25, 2024
2e79e5e
Minor changes
burkeds Jun 25, 2024
1e984f5
Merging
burkeds Jun 26, 2024
5f8c111
updated
burkeds Jun 26, 2024
50a310c
Removed sardana example devices
burkeds Jun 27, 2024
c333324
Removed use of a specific tango signal type. tango signals in devices…
burkeds Jun 27, 2024
6c511c3
Test device for new repo structure
burkeds Jun 27, 2024
f378933
Simplifying tango base device
burkeds Jun 27, 2024
3a91768
Removing some frontend dependencies.
burkeds Jun 27, 2024
a43f087
new_sis3820 device appears to be working correctly. In this version a…
burkeds Jun 27, 2024
517f983
sis3820 rewritten for more generic format
burkeds Jun 28, 2024
3c0c0bf
Rewrote SIS3820Counter as a StandardReadable to show that the TangoRe…
burkeds Jun 28, 2024
dbf6c8f
Removed unused TangoSignal frontend
burkeds Jun 28, 2024
1e93b4e
minor changes
burkeds Jun 28, 2024
66e3188
Updated TangoReadableDevice docstring
burkeds Jun 28, 2024
ee1166b
If events are not support, start an async polling process
burkeds Jun 28, 2024
f2c5f43
Improved polling. Added methods to TangoTransport to allow events and…
burkeds Jul 2, 2024
d4379c4
Simplified callback attributes
burkeds Jul 3, 2024
f8a017b
Refactor to use WatchableAsyncStatus
burkeds Jul 3, 2024
2abc525
SignalCache needs an initial reading
burkeds Jul 4, 2024
c5a1a74
Updated motor to use cached signals now that polling is enabled by de…
burkeds Jul 4, 2024
0aab38a
Removed is_cachable. Passing None to allow_events will return the cur…
burkeds Jul 4, 2024
7583ce9
updated timer example class
burkeds Jul 4, 2024
810fad0
Removed tango_mover
burkeds Jul 4, 2024
5f502ce
Removed commented code
burkeds Jul 4, 2024
856fc25
wip, fixing tests
burkeds Jul 5, 2024
d8c8af3
Poll wasn't correctly handling if/else statements when checking value…
burkeds Jul 5, 2024
06a9911
Removed unused TangoMover
burkeds Jul 5, 2024
4a8aa4f
Removed tests that explicitly use external running tango servers
burkeds Jul 5, 2024
ec03fdf
linting fix
burkeds Jul 5, 2024
7648fc4
Modified testing for new backend.
burkeds Jul 5, 2024
1333d0e
Modified tests to use asynchronous DeviceProxy
burkeds Jul 8, 2024
5454f6b
Merge branch 'main' into new_merge
burkeds Jul 8, 2024
dc2cf08
Added pytango to dependency list
burkeds Jul 8, 2024
f08a213
Removed redundant decorator
burkeds Jul 8, 2024
7c112fc
Added back in optional call to register_signals() after the proxy is …
burkeds Jul 10, 2024
219d2d9
Fixed docs by removing old generated files.
burkeds Jul 10, 2024
2d9ec40
Updated pytango version to solve deprecation warning in calls to test…
burkeds Jul 12, 2024
83cb195
Merge branch 'main' into new_merge
burkeds Jul 12, 2024
b26c239
Tango tests can only be run as forked pytests, meaning they are each …
burkeds Jul 12, 2024
2744bfe
Fixed line length.
burkeds Jul 12, 2024
504639b
Fixed linting again
burkeds Jul 12, 2024
e255244
More linting fixes.
burkeds Jul 12, 2024
00244e2
dgg2 should config and read the sample time
burkeds Jul 22, 2024
02a8813
Removed example devices which were only valid for Tango servers at DESY.
burkeds Aug 5, 2024
9db25b2
Removed unused, commented code
burkeds Aug 5, 2024
f858347
Removed unnecessary print statements
burkeds Aug 6, 2024
8a8d4df
New tests for tango_transport and some modification to test devices
burkeds Aug 12, 2024
1c09962
Merging main
burkeds Aug 12, 2024
f6d5642
Redefining test devices in test_tango_transport. Ruff didn't like imp…
burkeds Aug 12, 2024
f3932f1
Fixed import from core._utils
burkeds Aug 12, 2024
da15290
Improved test coverage
burkeds Aug 13, 2024
17013b6
wip to improve coverage to TangoProxy.put()
burkeds Aug 13, 2024
20edee3
write_attribute_asynch doesn't need to be awaited
burkeds Aug 13, 2024
0d14e64
put will now timeout properly
burkeds Aug 13, 2024
14cb598
New implementation of put without using timeout arg of _reply functions
burkeds Aug 14, 2024
31303b5
call to set_name required after registering signals
burkeds Aug 14, 2024
06f8956
Improving coverage to base device
burkeds Aug 14, 2024
1193e43
Added get_w_value to CommandProxy so that locate() works when it is t…
burkeds Aug 14, 2024
ad7e93f
Improved coverage for auto signals. Moved check for nonexistend attri…
burkeds Aug 14, 2024
c2f88ef
Removed redundant check for tr_name
burkeds Aug 15, 2024
a113923
Improved poll testing coverage and added tests for arrays and strings
burkeds Aug 15, 2024
3aefd07
Added tests for polling exceptions for nonexistent attributes and com…
burkeds Aug 15, 2024
4fc9c9f
Added test for a TangoTransport with separate read/write TRLs
burkeds Aug 15, 2024
31e59a1
Added tests to catch device failed exceptions when putting.
burkeds Aug 15, 2024
969db94
Improved coverage of TangoTransport exceptions and added protection f…
burkeds Aug 15, 2024
6d41773
Added exhaustive testing of automatic signals including cases where a…
burkeds Aug 19, 2024
648aa5c
Added methods for tango_signals to automatically infer the datatype i…
burkeds Aug 20, 2024
aaaae49
Fixed base device tests. Made datatype optional for tango signals
burkeds Aug 20, 2024
30f0de9
Forgot to add calls to infer python types in non-auto signals
burkeds Aug 22, 2024
8b53af4
minor layout changes
burkeds Aug 22, 2024
b628390
make_backend and infer_python_type added to init
burkeds Aug 22, 2024
6f87509
Moved inference of signal frontend to its own method so that its call…
burkeds Aug 22, 2024
b2b9cc7
minor typehint change
burkeds Aug 22, 2024
34c5143
Updated imports in init
burkeds Aug 22, 2024
6330c5b
Demo for tango test devices
burkeds Aug 22, 2024
7a663f0
Merge branch 'demo' into 'tango_support'
burkeds Aug 22, 2024
7c7372b
Added demo test
burkeds Aug 22, 2024
da1971c
Renamed TangoTransport to TangoSignalBackend for codebase consistency.
burkeds Aug 26, 2024
eb1fd54
Wrapped trigger in status decorator to more cleanly return a status o…
burkeds Aug 26, 2024
3a004ee
Refactored TangoReadableDevice to TangoDevice and TangoReadable. Refa…
burkeds Aug 26, 2024
9b526fd
Added class decorator tango_polling to disable event driven updates i…
burkeds Aug 26, 2024
e9f909a
Refactored to move _backend subpackage to signal. Renamed modules to …
burkeds Aug 26, 2024
7c69f55
Moved setting polling parameters of backend to TangoDevice::register_…
burkeds Aug 26, 2024
48a18f0
Merge branch 'bluesky:main' into tango_support
burkeds Aug 27, 2024
e8dfa71
Improved coverage of base device testing.
burkeds Aug 27, 2024
72d4d1d
Increased timeout on a few tests as they would infrequently fail from…
burkeds Aug 27, 2024
e301cb5
Simplified how children are created from annotations
burkeds Aug 27, 2024
c0e2079
Remove method separators.
burkeds Sep 2, 2024
fa2256c
Merge branch 'main' into tango_support
Sep 5, 2024
063bf7a
Updated pyproject.toml so that pytango can be installed with pip inst…
burkeds Sep 5, 2024
3a91d77
tango_polling will take a tuple to enable polling across all signals …
burkeds Sep 5, 2024
d59fb5f
Fixed some tests.
burkeds Sep 5, 2024
a1a0410
Moved tango_demo to docs.
burkeds Sep 5, 2024
d18607e
Somehow the polling changes didn't make it into a previous commit
burkeds Sep 6, 2024
f66d403
When creating a tango device, the signal type is now required in type…
burkeds Sep 12, 2024
e586a24
Improved test covereage of demo devices. Added composite device Tango…
burkeds Sep 12, 2024
73f8e5d
Updated tango demo in docs
burkeds Sep 12, 2024
b5f9787
Fix tests to allow for TangoReadables without a trl/proxy.
burkeds Sep 12, 2024
528be15
Merge branch 'main' into merge
burkeds Sep 12, 2024
6d4284a
Refactored to allow for creation of tango devices without a trl such …
burkeds Sep 18, 2024
b9e2ccc
Merge branch 'main' into tango_support
burkeds Sep 18, 2024
6cdaca8
Replaced wait_for_idle in the demo motor with an AsyncStatus wrapped …
burkeds Sep 18, 2024
518073e
Merge branch 'main' into tango_support
burkeds Sep 19, 2024
1e8a00a
New formatting to comply with ruff
burkeds Sep 19, 2024
05826ec
pyright fixes
burkeds Sep 19, 2024
9ce9615
pyright fixes
burkeds Sep 19, 2024
e9be82e
pyright fixes
burkeds Sep 20, 2024
ce32238
Complying with pyright
burkeds Sep 25, 2024
329179c
Trigger CI
burkeds Sep 30, 2024
4bb7853
Fixing tests and docs
burkeds Sep 30, 2024
8f121dc
Fixing tests and docs
burkeds Sep 30, 2024
0e90e93
Merge branch 'main' into merge
burkeds Sep 30, 2024
e022d11
Fixed indentation for docs
burkeds Sep 30, 2024
4238fb3
Fixing indentation
burkeds Sep 30, 2024
ea1b637
Fixing docs
burkeds Sep 30, 2024
c029407
Fixing docs
burkeds Sep 30, 2024
632eccc
improving coverage
burkeds Sep 30, 2024
b05024e
detector demo device is now a standard readable. Tango devices can au…
burkeds Oct 7, 2024
592b6c1
Merging main branch
burkeds Oct 8, 2024
e289ddd
Adding support for the Tango control system (https://www.tango-contro…
burkeds Oct 8, 2024
8f8c2c3
Adding support for the Tango control system (https://www.tango-contro…
burkeds Oct 8, 2024
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
54 changes: 54 additions & 0 deletions docs/examples/tango_demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import asyncio

import bluesky.plan_stubs as bps
import bluesky.plans as bp
from bluesky import RunEngine

from ophyd_async.tango.demo import (
DemoCounter,
DemoMover,
TangoDetector,
)
from tango.test_context import MultiDeviceTestContext

content = (
{
"class": DemoMover,
"devices": [{"name": "demo/motor/1"}],
},
{
"class": DemoCounter,
"devices": [{"name": "demo/counter/1"}, {"name": "demo/counter/2"}],
},
)

tango_context = MultiDeviceTestContext(content)


async def main():
with tango_context:
detector = TangoDetector(
trl="",
name="detector",
counters_kwargs={"prefix": "demo/counter/", "count": 2},
mover_kwargs={"trl": "demo/motor/1"},
)
await detector.connect()

RE = RunEngine()

RE(bps.read(detector))
RE(bps.mv(detector, 0))
RE(bp.count(list(detector.counters.values())))

set_status = detector.set(1.0)
await asyncio.sleep(0.1)
stop_status = detector.stop()
await set_status
await stop_status
assert all([set_status.done, stop_status.done])
assert all([set_status.success, stop_status.success])


if __name__ == "__main__":
asyncio.run(main())
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,12 @@ requires-python = ">=3.10"
ca = ["aioca>=1.6"]
pva = ["p4p"]
sim = ["h5py"]
tango = ["pytango>=10.0.0rc1"]
dev = [
"ophyd_async[pva]",
"ophyd_async[sim]",
"ophyd_async[ca]",
"ophyd_async[tango]",
"black",
"flake8",
"flake8-isort",
Expand All @@ -58,6 +60,7 @@ dev = [
"pytest-asyncio",
"pytest-cov",
"pytest-faulthandler",
"pytest-forked",
"pytest-rerunfailures",
"pytest-timeout",
"ruff",
Expand Down
45 changes: 45 additions & 0 deletions src/ophyd_async/tango/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from .base_devices import (
TangoDevice,
TangoReadable,
tango_polling,
)
from .signal import (
AttributeProxy,
CommandProxy,
TangoSignalBackend,
ensure_proper_executor,
get_dtype_extended,
get_python_type,
get_tango_trl,
get_trl_descriptor,
infer_python_type,
infer_signal_character,
make_backend,
tango_signal_auto,
tango_signal_r,
tango_signal_rw,
tango_signal_w,
tango_signal_x,
)

__all__ = [
TangoDevice,
TangoReadable,
tango_polling,
TangoSignalBackend,
get_python_type,
get_dtype_extended,
get_trl_descriptor,
get_tango_trl,
infer_python_type,
infer_signal_character,
make_backend,
AttributeProxy,
CommandProxy,
ensure_proper_executor,
tango_signal_auto,
tango_signal_r,
tango_signal_rw,
tango_signal_w,
tango_signal_x,
]
4 changes: 4 additions & 0 deletions src/ophyd_async/tango/base_devices/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from ._base_device import TangoDevice, tango_polling
from ._tango_readable import TangoReadable

__all__ = ["TangoDevice", "TangoReadable", "tango_polling"]
243 changes: 243 additions & 0 deletions src/ophyd_async/tango/base_devices/_base_device.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
from __future__ import annotations

from typing import (
Dict,
Optional,
Tuple,
Type,
TypeVar,
Union,
get_args,
get_origin,
get_type_hints,
)

from ophyd_async.core import (
DEFAULT_TIMEOUT,
Device,
DeviceVector,
Signal,
)
from ophyd_async.tango.signal import (
make_backend,
tango_signal_auto,
)
from tango import DeviceProxy as SyncDeviceProxy
from tango.asyncio import DeviceProxy as AsyncDeviceProxy

T = TypeVar("T")


class TangoDevice(Device):
callumforrester marked this conversation as resolved.
Show resolved Hide resolved
"""
General class for TangoDevices. Extends Device to provide attributes for Tango
devices.

Parameters
----------
trl: str
Tango resource locator, typically of the device server.
device_proxy: Optional[Union[AsyncDeviceProxy, SyncDeviceProxy]]
Asynchronous or synchronous DeviceProxy object for the device. If not provided,
an asynchronous DeviceProxy object will be created using the trl and awaited
when the device is connected.
"""

trl: str = ""
proxy: Optional[Union[AsyncDeviceProxy, SyncDeviceProxy]] = None
_polling: Tuple[bool, float, float, float] = (False, 0.1, None, 0.1)
_signal_polling: Dict[str, Tuple[bool, float, float, float]] = {}
_poll_only_annotated_signals: bool = True

def __init__(
self,
trl: Optional[str] = None,
device_proxy: Optional[Union[AsyncDeviceProxy, SyncDeviceProxy]] = None,
name: str = "",
) -> None:
self.trl = trl if trl else ""
self.proxy = device_proxy
tango_create_children_from_annotations(self)
super().__init__(name=name)

def set_trl(self, trl: str):
"""Set the Tango resource locator."""
if not isinstance(trl, str):
raise ValueError("TRL must be a string.")
self.trl = trl

async def connect(
self,
mock: bool = False,
timeout: float = DEFAULT_TIMEOUT,
force_reconnect: bool = False,
):
if self.trl and self.proxy is None:
self.proxy = await AsyncDeviceProxy(self.trl)
elif self.proxy and not self.trl:
self.trl = self.proxy.name()

# Set the trl of the signal backends
for child in self.children():
if isinstance(child[1], Signal):
resource_name = child[0].lstrip("_")
read_trl = f"{self.trl}/{resource_name}"
child[1]._backend.set_trl(read_trl, read_trl) # noqa: SLF001

if self.proxy is not None:
self.register_signals()
await fill_proxy_entries(self)

# set_name should be called again to propagate the new signal names
self.set_name(self.name)

# Set the polling configuration
if self._polling[0]:
for child in self.children():
if issubclass(type(child[1]), Signal):
child[1]._backend.set_polling(*self._polling) # noqa: SLF001
child[1]._backend.allow_events(False) # noqa: SLF001
if self._signal_polling:
for signal_name, polling in self._signal_polling.items():
if hasattr(self, signal_name):
attr = getattr(self, signal_name)
attr._backend.set_polling(*polling) # noqa: SLF001
attr._backend.allow_events(False) # noqa: SLF001

await super().connect(mock=mock, timeout=timeout)

# Users can override this method to register new signals
def register_signals(self):
pass


def tango_polling(
polling: Optional[
Union[Tuple[float, float, float], Dict[str, Tuple[float, float, float]]]
] = None,
signal_polling: Optional[Dict[str, Tuple[float, float, float]]] = None,
):
"""
Class decorator to configure polling for Tango devices.

This decorator allows for the configuration of both device-level and signal-level
polling for Tango devices. Polling is useful for device servers that do not support
event-driven updates.

Parameters
----------
polling : Optional[Union[Tuple[float, float, float],
Dict[str, Tuple[float, float, float]]]], optional
Device-level polling configuration as a tuple of three floats representing the
polling interval, polling timeout, and polling delay. Alternatively,
a dictionary can be provided to specify signal-level polling configurations
directly.
signal_polling : Optional[Dict[str, Tuple[float, float, float]]], optional
Signal-level polling configuration as a dictionary where keys are signal names
and values are tuples of three floats representing the polling interval, polling
timeout, and polling delay.

Returns
-------
Callable
A class decorator that sets the `_polling` and `_signal_polling` attributes on
the decorated class.

Example
-------
Device-level and signal-level polling:
@tango_polling(
polling=(0.5, 1.0, 0.1),
signal_polling={
'signal1': (0.5, 1.0, 0.1),
'signal2': (1.0, 2.0, 0.2),
}
)
class MyTangoDevice(TangoDevice):
signal1: Signal
signal2: Signal
"""
if isinstance(polling, dict):
signal_polling = polling
polling = None

def decorator(cls):
if polling is not None:
cls._polling = (True, *polling)
if signal_polling is not None:
cls._signal_polling = {k: (True, *v) for k, v in signal_polling.items()}
return cls

return decorator


def tango_create_children_from_annotations(
device: TangoDevice, included_optional_fields: Tuple[str, ...] = ()
):
"""Initialize blocks at __init__ of `device`."""
for name, device_type in get_type_hints(type(device)).items():
if name in ("_name", "parent"):
continue

device_type, is_optional = _strip_union(device_type)
if is_optional and name not in included_optional_fields:
continue

is_device_vector, device_type = _strip_device_vector(device_type)
if is_device_vector:
n_device_vector = DeviceVector()
setattr(device, name, n_device_vector)

else:
origin = get_origin(device_type)
origin = origin if origin else device_type

if issubclass(origin, Signal):
type_args = get_args(device_type)
datatype = type_args[0] if type_args else None
backend = make_backend(datatype=datatype, device_proxy=device.proxy)
setattr(device, name, origin(name=name, backend=backend))

elif issubclass(origin, Device) or isinstance(origin, Device):
setattr(device, name, origin())


async def fill_proxy_entries(device: TangoDevice):
proxy_trl = device.trl
children = [name.lstrip("_") for name, _ in device.children()]
proxy_attributes = list(device.proxy.get_attribute_list())
proxy_commands = list(device.proxy.get_command_list())
combined = proxy_attributes + proxy_commands

for name in combined:
if name not in children:
full_trl = f"{proxy_trl}/{name}"
try:
auto_signal = await tango_signal_auto(
trl=full_trl, device_proxy=device.proxy
)
setattr(device, name, auto_signal)
except RuntimeError as e:
if "Commands with different in and out dtypes" in str(e):
print(
f"Skipping {name}. Commands with different in and out dtypes"
f" are not supported."
)
continue
raise e


def _strip_union(field: Union[Union[T], T]) -> Tuple[T, bool]:
if get_origin(field) is Union:
args = get_args(field)
is_optional = type(None) in args
for arg in args:
if arg is not type(None):
return arg, is_optional
return field, False


def _strip_device_vector(field: Union[Type[Device]]) -> Tuple[bool, Type[Device]]:
if get_origin(field) is DeviceVector:
return True, get_args(field)[0]
return False, field
Loading