Skip to content

Commit 395d75d

Browse files
committed
merged blocks and fields into a single recursive controller
1 parent 139b705 commit 395d75d

File tree

10 files changed

+248
-451
lines changed

10 files changed

+248
-451
lines changed

src/fastcs_pandablocks/__init__.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,12 @@
1010
from ._version import __version__
1111
from .gui import PandaGUIOptions
1212
from .panda.controller import PandaController
13-
from .types import EpicsName
1413

1514
DEFAULT_POLL_PERIOD = 0.1
1615

1716

1817
def ioc(
19-
epics_prefix: EpicsName,
18+
epics_prefix: str,
2019
hostname: str,
2120
screens_directory: Path | None = None,
2221
clear_bobfiles: bool = False,
@@ -31,7 +30,7 @@ def ioc(
3130

3231
controller = PandaController(hostname, poll_period)
3332
backend = EpicsBackend(
34-
controller, pv_prefix=str(epics_prefix), ioc_options=epics_ioc_options
33+
controller, pv_prefix=epics_prefix, ioc_options=epics_ioc_options
3534
)
3635

3736
if clear_bobfiles and not screens_directory:

src/fastcs_pandablocks/__main__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
from fastcs.backends.epics.util import PvNamingConvention
88

99
from fastcs_pandablocks import DEFAULT_POLL_PERIOD, ioc
10-
from fastcs_pandablocks.types import EpicsName
1110

1211
from . import __version__
1312

@@ -83,7 +82,7 @@ def main():
8382
logging.basicConfig(format="%(levelname)s:%(message)s", level=level)
8483

8584
ioc(
86-
EpicsName(prefix=parsed_args.prefix),
85+
parsed_args.prefix,
8786
parsed_args.hostname,
8887
screens_directory=Path(parsed_args.screens_dir),
8988
clear_bobfiles=parsed_args.clear_bobfiles,

src/fastcs_pandablocks/panda/blocks.py

Lines changed: 0 additions & 146 deletions
This file was deleted.

src/fastcs_pandablocks/panda/client_wrapper.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@
1414
)
1515

1616
from fastcs_pandablocks.types import (
17+
PandaName,
1718
RawBlocksType,
1819
RawFieldsType,
1920
RawInitialValuesType,
2021
)
21-
from fastcs_pandablocks.types.string_types import PandaName
2222

2323

2424
class RawPanda:
Lines changed: 87 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,119 @@
11
import asyncio
22

3+
from fastcs.attributes import Attribute, AttrR, AttrRW, AttrW
34
from fastcs.controller import Controller
45
from fastcs.wrappers import scan
56

6-
from fastcs_pandablocks.types import PandaName
7+
from fastcs_pandablocks.types import (
8+
PandaName,
9+
RawBlocksType,
10+
RawFieldsType,
11+
RawInitialValuesType,
12+
)
713

8-
from .blocks import Blocks
914
from .client_wrapper import RawPanda
15+
from .fields import FieldController
16+
17+
18+
def _parse_introspected_data(
19+
blocks: RawBlocksType,
20+
field_infos: RawFieldsType,
21+
labels: RawInitialValuesType,
22+
initial_values: RawInitialValuesType,
23+
):
24+
block_controllers: dict[PandaName, FieldController] = {}
25+
for (block_name, block_info), field_info in zip(
26+
blocks.items(), field_infos, strict=True
27+
):
28+
numbered_block_names = (
29+
[block_name]
30+
if block_info.number in (None, 1)
31+
else [
32+
block_name + PandaName(block_number=number)
33+
for number in range(1, block_info.number + 1)
34+
]
35+
)
36+
for numbered_block_name in numbered_block_names:
37+
block_initial_values = {
38+
key: value
39+
for key, value in initial_values.items()
40+
if key in numbered_block_name
41+
}
42+
label = labels.get(numbered_block_name, None)
43+
block = FieldController(
44+
numbered_block_name,
45+
label=block_info.description or label,
46+
)
47+
block.make_sub_fields(field_info, block_initial_values)
48+
block_controllers[numbered_block_name] = block
49+
50+
return block_controllers
1051

1152

1253
class PandaController(Controller):
1354
def __init__(self, hostname: str, poll_period: float) -> None:
55+
# TODO https://github.com/DiamondLightSource/FastCS/issues/62
56+
self.poll_period = poll_period
57+
58+
self._additional_attributes: dict[str, Attribute] = {}
1459
self._raw_panda = RawPanda(hostname)
15-
self._blocks = Blocks()
16-
self.is_connected = False
60+
_blocks: dict[PandaName, FieldController] = {}
1761

1862
super().__init__()
1963

20-
async def connect(self) -> None:
21-
if self.is_connected:
22-
return
64+
@property
65+
def additional_attributes(self):
66+
return self._additional_attributes
2367

68+
async def connect(self) -> None:
2469
await self._raw_panda.connect()
2570
blocks, fields, labels, initial_values = await self._raw_panda.introspect()
26-
27-
self._blocks.parse_introspected_data(blocks, fields, labels, initial_values)
28-
self.is_connected = True
71+
self._blocks = _parse_introspected_data(blocks, fields, labels, initial_values)
2972

3073
async def initialise(self) -> None:
3174
await self.connect()
75+
for block_name, block in self._blocks.items():
76+
if block.top_level_attribute is not None:
77+
self._additional_attributes[block_name.attribute_name] = (
78+
block.top_level_attribute
79+
)
80+
if block.additional_attributes or block.sub_fields:
81+
self.register_sub_controller(block_name.attribute_name, block)
82+
await block.initialise()
83+
84+
def get_attribute(self, panda_name: PandaName) -> Attribute:
85+
assert panda_name.block
86+
block_controller = self._blocks[panda_name.up_to_block()]
87+
if panda_name.field is None:
88+
assert block_controller.top_level_attribute is not None
89+
return block_controller.top_level_attribute
90+
91+
field_controller = block_controller.sub_fields[panda_name.up_to_field()]
92+
if panda_name.sub_field is None:
93+
assert field_controller.top_level_attribute is not None
94+
return field_controller.top_level_attribute
3295

33-
for attr_name, controller in self._blocks.flattened_attribute_tree():
34-
self.register_sub_controller(attr_name, controller)
35-
controller.initialise()
96+
sub_field_controller = field_controller.sub_fields[panda_name]
97+
assert sub_field_controller.top_level_attribute is not None
98+
return sub_field_controller.top_level_attribute
99+
100+
async def update_field_value(self, panda_name: PandaName, value: str):
101+
attribute = self.get_attribute(panda_name)
102+
103+
if isinstance(attribute, AttrW):
104+
await attribute.process(value)
105+
elif isinstance(attribute, (AttrRW | AttrR)):
106+
await attribute.set(value)
107+
else:
108+
raise RuntimeError(f"Couldn't find panda field for {panda_name}.")
36109

37-
# TODO https://github.com/DiamondLightSource/FastCS/issues/62
38110
@scan(0.1)
39111
async def update(self):
40112
await self._raw_panda.get_changes()
41113
assert self._raw_panda.changes
42114
await asyncio.gather(
43115
*[
44-
self._blocks.update_field_value(
45-
PandaName.from_string(raw_panda_name), value
46-
)
116+
self.update_field_value(PandaName.from_string(raw_panda_name), value)
47117
for raw_panda_name, value in self._raw_panda.changes.items()
48118
]
49119
)

0 commit comments

Comments
 (0)