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
46 changes: 17 additions & 29 deletions src/fastcs/backends/tango/dsr.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from types import MethodType
from typing import Any

import tango
Expand All @@ -16,7 +15,6 @@
@dataclass
class TangoDSROptions:
dev_name: str = "MY/DEVICE/NAME"
dev_class: str = "FAST_CS_DEVICE"
dsr_instance: str = "MY_SERVER_INSTANCE"
debug: bool = False

Expand All @@ -25,23 +23,12 @@
attr_name: str, attribute: AttrR, controller: BaseController
) -> Callable[[Any], Any]:
async def fget(tango_device: Device):
assert attribute.updater is not None

await attribute.updater.update(controller, attribute)
tango_device.info_stream(f"called fget method: {attr_name}")
return attribute.get()

return fget


def _tango_polling_period(attribute: AttrR) -> int:
if attribute.updater is not None:
# Convert to integer milliseconds
return int(attribute.updater.update_period * 1000)

return -1 # `tango.server.attribute` default for `polling_period`


def _tango_display_format(attribute: Attribute) -> str:
match attribute.datatype:
case Float(prec):
Expand All @@ -54,10 +41,8 @@
attr_name: str, attribute: AttrW, controller: BaseController
) -> Callable[[Any, Any], Any]:
async def fset(tango_device: Device, val):
assert attribute.sender is not None

await attribute.sender.put(controller, attribute, val)
tango_device.info_stream(f"called fset method: {attr_name}")
await attribute.process(val)

return fset

Expand All @@ -84,7 +69,6 @@
),
access=AttrWriteType.READ_WRITE,
format=_tango_display_format(attribute),
polling_period=_tango_polling_period(attribute),
)
case AttrR():
collection[d_attr_name] = server.attribute(
Expand All @@ -95,7 +79,6 @@
attr_name, attribute, single_mapping.controller
),
format=_tango_display_format(attribute),
polling_period=_tango_polling_period(attribute),
)
case AttrW():
collection[d_attr_name] = server.attribute(
Expand All @@ -115,8 +98,10 @@
method_name: str, method: Callable, controller: BaseController
) -> Callable[..., Awaitable[None]]:
async def _dynamic_f(tango_device: Device) -> None:
tango_device.info_stream(f"called {controller} f method: {method_name}")
return await MethodType(method, controller)()
tango_device.info_stream(
f"called {'_'.join(controller.path)} f method: {method_name}"
)
return await getattr(controller, method.__name__)()

_dynamic_f.__name__ = method_name
return _dynamic_f
Expand Down Expand Up @@ -146,7 +131,6 @@
async def init_device(tango_device: Device):
await server.Device.init_device(tango_device) # type: ignore
tango_device.set_state(DevState.ON)
await mapping.controller.connect()

return {"init_device": init_device}

Expand All @@ -171,11 +155,10 @@
class TangoDSR:
def __init__(self, mapping: Mapping):
self._mapping = mapping
self.dev_class = self._mapping.controller.__class__.__name__
self._device = self._create_device()

def run(self, options: TangoDSROptions | None = None) -> None:
if options is None:
options = TangoDSROptions()

def _create_device(self):
class_dict: dict = {
**_collect_dev_attributes(self._mapping),
**_collect_dev_commands(self._mapping),
Expand All @@ -185,14 +168,19 @@
}

class_bases = (server.Device,)
pytango_class = type(options.dev_class, class_bases, class_dict)
register_dev(options.dev_name, options.dev_class, options.dsr_instance)
pytango_class = type(self.dev_class, class_bases, class_dict)
return pytango_class

def run(self, options: TangoDSROptions | None = None) -> None:
if options is None:
options = TangoDSROptions()

Check warning on line 176 in src/fastcs/backends/tango/dsr.py

View check run for this annotation

Codecov / codecov/patch

src/fastcs/backends/tango/dsr.py#L175-L176

Added lines #L175 - L176 were not covered by tests

dsr_args = _collect_dsr_args(options)

server.run(
(pytango_class,),
[options.dev_class, options.dsr_instance, *dsr_args],
(self._device,),
[self.dev_class, options.dsr_instance, *dsr_args],
green_mode=server.GreenMode.Asyncio,
)


Expand Down
24 changes: 24 additions & 0 deletions tests/backends/epics/test_gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
LED,
ButtonPanel,
ComboBox,
Group,
SignalR,
SignalRW,
SignalW,
SignalX,
SubScreen,
TextFormat,
TextRead,
TextWrite,
Expand All @@ -30,6 +32,28 @@ def test_get_components(mapping):

components = gui.extract_mapping_components(mapping.get_controller_mappings()[0])
assert components == [
Group(
name="SubController01",
layout=SubScreen(labelled=True),
children=[
SignalR(
name="ReadInt",
read_pv="DEVICE:SubController01:ReadInt",
read_widget=TextRead(),
)
],
),
Group(
name="SubController02",
layout=SubScreen(labelled=True),
children=[
SignalR(
name="ReadInt",
read_pv="DEVICE:SubController01:ReadInt",
read_widget=TextRead(),
)
],
),
SignalR(name="BigEnum", read_pv="DEVICE:BigEnum", read_widget=TextRead()),
SignalR(name="ReadBool", read_pv="DEVICE:ReadBool", read_widget=LED()),
SignalR(
Expand Down
147 changes: 111 additions & 36 deletions tests/backends/tango/test_dsr.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,112 @@
import pytest
from pytest_mock import MockerFixture
from tango._tango import AttrWriteType, CmdArgType

from fastcs.backends.tango.dsr import _collect_dev_attributes, _collect_dev_commands


def test_collect_attributes(mapping):
attributes = _collect_dev_attributes(mapping)

# Check that attributes are created and of expected type
assert list(attributes.keys()) == [
"BigEnum",
"ReadBool",
"ReadInt",
"ReadWriteFloat",
"ReadWriteInt",
"StringEnum",
"WriteBool",
]
assert attributes["ReadInt"].attr_write == AttrWriteType.READ
assert attributes["ReadInt"].attr_type == CmdArgType.DevLong64
assert attributes["StringEnum"].attr_write == AttrWriteType.READ_WRITE
assert attributes["StringEnum"].attr_type == CmdArgType.DevString
assert attributes["ReadWriteFloat"].attr_write == AttrWriteType.READ_WRITE
assert attributes["ReadWriteFloat"].attr_type == CmdArgType.DevDouble
assert attributes["WriteBool"].attr_write == AttrWriteType.WRITE
assert attributes["WriteBool"].attr_type == CmdArgType.DevBoolean


@pytest.mark.asyncio
async def test_collect_commands(mapping, mocker: MockerFixture):
commands = _collect_dev_commands(mapping)

# Check that command is created and it can be called
assert list(commands.keys()) == ["Go"]
await commands["Go"](mocker.MagicMock())
from tango import DevState
from tango.test_context import DeviceTestContext

from fastcs.backends.tango.backend import TangoBackend


class TestTangoDevice:
@pytest.fixture(scope="class")
def tango_context(self, assertable_controller):
# https://tango-controls.readthedocs.io/projects/pytango/en/v9.5.1/testing/test_context.html
device = TangoBackend(assertable_controller)._dsr._device
with DeviceTestContext(device) as proxy:
yield proxy

def test_list_attributes(self, tango_context):
assert list(tango_context.get_attribute_list()) == [
"BigEnum",
"ReadBool",
"ReadInt",
"ReadWriteFloat",
"ReadWriteInt",
"StringEnum",
"WriteBool",
"SubController01_ReadInt",
"SubController02_ReadInt",
"State",
"Status",
]

def test_list_commands(self, tango_context):
assert list(tango_context.get_command_list()) == [
"Go",
"Init",
"State",
"Status",
]

def test_state(self, tango_context):
assert tango_context.command_inout("State") == DevState.ON

def test_status(self, tango_context):
expect = "The device is in ON state."
assert tango_context.command_inout("Status") == expect

def test_read_int(self, assertable_controller, tango_context):
expect = 0
with assertable_controller.assert_read_here(["read_int"]):
result = tango_context.read_attribute("ReadInt").value
assert result == expect

def test_read_write_int(self, assertable_controller, tango_context):
expect = 0
with assertable_controller.assert_read_here(["read_write_int"]):
result = tango_context.read_attribute("ReadWriteInt").value
assert result == expect
new = 9
with assertable_controller.assert_write_here(["read_write_int"]):
tango_context.write_attribute("ReadWriteInt", new)
assert tango_context.read_attribute("ReadWriteInt").value == new

def test_read_write_float(self, assertable_controller, tango_context):
expect = 0.0
with assertable_controller.assert_read_here(["read_write_float"]):
result = tango_context.read_attribute("ReadWriteFloat").value
assert result == expect
new = 0.5
with assertable_controller.assert_write_here(["read_write_float"]):
tango_context.write_attribute("ReadWriteFloat", new)
assert tango_context.read_attribute("ReadWriteFloat").value == new

def test_read_bool(self, assertable_controller, tango_context):
expect = False
with assertable_controller.assert_read_here(["read_bool"]):
result = tango_context.read_attribute("ReadBool").value
assert result == expect

def test_write_bool(self, assertable_controller, tango_context):
with assertable_controller.assert_write_here(["write_bool"]):
tango_context.write_attribute("WriteBool", True)

def test_string_enum(self, assertable_controller, tango_context):
expect = ""
with assertable_controller.assert_read_here(["string_enum"]):
result = tango_context.read_attribute("StringEnum").value
assert result == expect
new = "new"
with assertable_controller.assert_write_here(["string_enum"]):
tango_context.write_attribute("StringEnum", new)
assert tango_context.read_attribute("StringEnum").value == new

def test_big_enum(self, assertable_controller, tango_context):
expect = 0
with assertable_controller.assert_read_here(["big_enum"]):
result = tango_context.read_attribute("BigEnum").value
assert result == expect

def test_go(self, assertable_controller, tango_context):
with assertable_controller.assert_execute_here(["go"]):
tango_context.command_inout("Go")

def test_read_child1(self, assertable_controller, tango_context):
expect = 0
with assertable_controller.assert_read_here(["SubController01", "read_int"]):
result = tango_context.read_attribute("SubController01_ReadInt").value
assert result == expect

def test_read_child2(self, assertable_controller, tango_context):
expect = 0
with assertable_controller.assert_read_here(["SubController02", "read_int"]):
result = tango_context.read_attribute("SubController02_ReadInt").value
assert result == expect
Loading
Loading