Skip to content

Commit bb67852

Browse files
authored
Add update to ConfigFanAttributeIO (#93)
We now set the value of the fan out only if all the underlying attributes are the same, else to the default value of the DataType. In future we will set an alarm when the underlying values are not the same. Update to fastcs 0.12 for all_equal helper.
1 parent 73b1e8f commit bb67852

File tree

5 files changed

+41
-14
lines changed

5 files changed

+41
-14
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ classifiers = [
1212
description = "FastCS support for the Odin detector software framework"
1313
dependencies = [
1414
"aiohttp",
15-
"fastcs[epicsca]~=0.11.2",
15+
"fastcs[epicsca]~=0.12.0a1",
1616
]
1717
dynamic = ["version"]
1818
license.file = "LICENSE"

src/fastcs_odin/controllers/odin_data/odin_data_adapter.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import logging
22
from collections.abc import Sequence
33

4-
from fastcs.attributes import AttributeIO, AttributeIORefT, AttrW
4+
from fastcs.attributes import AttributeIO, AttributeIORefT, AttrRW
55
from fastcs.controllers import ControllerVector
66
from fastcs.datatypes import DType_T
77

@@ -74,15 +74,15 @@ async def initialise(self):
7474

7575
def _create_config_fan_attributes(self):
7676
"""Search for config attributes in sub controllers to create fan out PVs."""
77-
parameter_attribute_map: dict[str, tuple[OdinParameter, list[AttrW]]] = {}
77+
parameter_attribute_map: dict[str, tuple[OdinParameter, list[AttrRW]]] = {}
7878
for sub_controller in get_all_sub_controllers(self):
7979
match sub_controller:
8080
case OdinSubController():
8181
for parameter in sub_controller.parameters:
8282
mode, key = parameter.uri[0], parameter.uri[-1]
8383
if mode == "config" and key not in self._unique_config:
8484
try:
85-
attr: AttrW = sub_controller.attributes[parameter.name] # type: ignore
85+
attr: AttrRW = sub_controller.attributes[parameter.name] # type: ignore
8686
if parameter.name not in parameter_attribute_map:
8787
parameter_attribute_map[parameter.name] = (
8888
parameter,

src/fastcs_odin/io/config_fan_sender_attribute_io.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import asyncio
2-
from dataclasses import dataclass
2+
from dataclasses import KW_ONLY, dataclass
33
from typing import Any
44

5-
from fastcs.attributes import AttributeIO, AttributeIORef, AttrRW, AttrW
5+
from fastcs.attributes import AttributeIO, AttributeIORef, AttrR, AttrRW, AttrW
66
from fastcs.datatypes import DType_T
77
from fastcs.logging import bind_logger
88

@@ -17,7 +17,9 @@ class ConfigFanAttributeIORef(AttributeIORef):
1717
attributes: A list of attributes to fan out to
1818
"""
1919

20-
attributes: list[AttrW]
20+
attributes: list[AttrRW]
21+
_: KW_ONLY
22+
update_period: float | None = 0.2
2123

2224

2325
class ConfigFanAttributeIO(AttributeIO[DType_T, ConfigFanAttributeIORef]):
@@ -32,5 +34,11 @@ async def send(self, attr: AttrW[DType_T, ConfigFanAttributeIORef], value: Any):
3234
]
3335
)
3436

35-
if isinstance(attr, AttrRW):
36-
await attr.update(value)
37+
async def update(self, attr: AttrR[DType_T, ConfigFanAttributeIORef]):
38+
values = [attribute.get() for attribute in attr.io_ref.attributes]
39+
40+
if attr.datatype.all_equal(values):
41+
await attr.update(values[0])
42+
else:
43+
# TODO: Set an alarm - https://github.com/DiamondLightSource/FastCS/issues/286
44+
await attr.update(attr.datatype.initial_value)

src/fastcs_odin/io/parameter_attribute_io.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@ async def update(self, attr: AttrR[DType_T, ParameterTreeAttributeIORef]) -> Non
4242
if parameter not in response:
4343
raise ValueError(f"{parameter} not found in response:\n{response}")
4444

45+
self.log_event(
46+
"Query for parameter",
47+
uri=attr.io_ref.path,
48+
response=response,
49+
topic=attr,
50+
)
51+
4552
value = response.get(parameter)
4653
await attr.update(attr.datatype.validate(value))
4754

tests/test_controllers.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -392,16 +392,28 @@ async def test_status_summary_updater_raise_exception_if_controller_not_found(
392392

393393
@pytest.mark.asyncio
394394
async def test_config_fan_sender(mocker: MockerFixture):
395-
attr1 = mocker.AsyncMock()
396-
attr2 = mocker.AsyncMock()
395+
attr1 = mocker.MagicMock()
396+
attr1.put = (put1_mock := mocker.AsyncMock())
397+
attr2 = mocker.MagicMock()
398+
attr2.put = (put2_mock := mocker.AsyncMock())
397399

398400
attr = AttrRW(Int(), ConfigFanAttributeIORef([attr1, attr2]))
399401
io = ConfigFanAttributeIO()
400402

401403
await io.send(attr, 10)
402-
attr1.put.assert_called_once_with(10, sync_setpoint=True)
403-
attr2.put.assert_called_once_with(10, sync_setpoint=True)
404-
assert attr.get() == 10
404+
put1_mock.assert_called_once_with(10, sync_setpoint=True)
405+
put2_mock.assert_called_once_with(10, sync_setpoint=True)
406+
407+
attr1.get.return_value = 10
408+
attr2.get.return_value = 5
409+
410+
await io.update(attr)
411+
assert attr.get() == 0 # attributes don't match -> default value
412+
413+
attr2.get.return_value = 10
414+
415+
await io.update(attr)
416+
assert attr.get() == 10 # attributes match -> set value
405417

406418

407419
@pytest.mark.asyncio

0 commit comments

Comments
 (0)