|
1 | 1 | import asyncio |
2 | 2 |
|
| 3 | +from fastcs.attributes import Attribute, AttrR, AttrRW, AttrW |
3 | 4 | from fastcs.controller import Controller |
4 | 5 | from fastcs.wrappers import scan |
5 | 6 |
|
6 | | -from fastcs_pandablocks.types import PandaName |
| 7 | +from fastcs_pandablocks.types import ( |
| 8 | + PandaName, |
| 9 | + RawBlocksType, |
| 10 | + RawFieldsType, |
| 11 | + RawInitialValuesType, |
| 12 | +) |
7 | 13 |
|
8 | | -from .blocks import Blocks |
9 | 14 | 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 |
10 | 51 |
|
11 | 52 |
|
12 | 53 | class PandaController(Controller): |
13 | 54 | 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] = {} |
14 | 59 | self._raw_panda = RawPanda(hostname) |
15 | | - self._blocks = Blocks() |
16 | | - self.is_connected = False |
| 60 | + _blocks: dict[PandaName, FieldController] = {} |
17 | 61 |
|
18 | 62 | super().__init__() |
19 | 63 |
|
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 |
23 | 67 |
|
| 68 | + async def connect(self) -> None: |
24 | 69 | await self._raw_panda.connect() |
25 | 70 | 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) |
29 | 72 |
|
30 | 73 | async def initialise(self) -> None: |
31 | 74 | 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 |
32 | 95 |
|
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}.") |
36 | 109 |
|
37 | | - # TODO https://github.com/DiamondLightSource/FastCS/issues/62 |
38 | 110 | @scan(0.1) |
39 | 111 | async def update(self): |
40 | 112 | await self._raw_panda.get_changes() |
41 | 113 | assert self._raw_panda.changes |
42 | 114 | await asyncio.gather( |
43 | 115 | *[ |
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) |
47 | 117 | for raw_panda_name, value in self._raw_panda.changes.items() |
48 | 118 | ] |
49 | 119 | ) |
0 commit comments