Skip to content

Commit

Permalink
merged blocks and fields into a single recursive controller
Browse files Browse the repository at this point in the history
  • Loading branch information
evalott100 committed Nov 1, 2024
1 parent 139b705 commit 030a2e1
Show file tree
Hide file tree
Showing 10 changed files with 395 additions and 475 deletions.
5 changes: 2 additions & 3 deletions src/fastcs_pandablocks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,12 @@
from ._version import __version__
from .gui import PandaGUIOptions
from .panda.controller import PandaController
from .types import EpicsName

DEFAULT_POLL_PERIOD = 0.1


def ioc(
epics_prefix: EpicsName,
epics_prefix: str,
hostname: str,
screens_directory: Path | None = None,
clear_bobfiles: bool = False,
Expand All @@ -31,7 +30,7 @@ def ioc(

controller = PandaController(hostname, poll_period)
backend = EpicsBackend(
controller, pv_prefix=str(epics_prefix), ioc_options=epics_ioc_options
controller, pv_prefix=epics_prefix, ioc_options=epics_ioc_options
)

if clear_bobfiles and not screens_directory:
Expand Down
3 changes: 1 addition & 2 deletions src/fastcs_pandablocks/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from fastcs.backends.epics.util import PvNamingConvention

from fastcs_pandablocks import DEFAULT_POLL_PERIOD, ioc
from fastcs_pandablocks.types import EpicsName

from . import __version__

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

ioc(
EpicsName(prefix=parsed_args.prefix),
parsed_args.prefix,
parsed_args.hostname,
screens_directory=Path(parsed_args.screens_dir),
clear_bobfiles=parsed_args.clear_bobfiles,
Expand Down
146 changes: 0 additions & 146 deletions src/fastcs_pandablocks/panda/blocks.py

This file was deleted.

14 changes: 3 additions & 11 deletions src/fastcs_pandablocks/panda/client_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,14 @@
)

from fastcs_pandablocks.types import (
PandaName,
RawBlocksType,
RawFieldsType,
RawInitialValuesType,
)
from fastcs_pandablocks.types.string_types import PandaName


class RawPanda:
changes: dict[str, str] | None = None

def __init__(self, hostname: str):
self._client = AsyncioClient(host=hostname)

Expand All @@ -32,14 +30,12 @@ async def connect(self):

async def disconnect(self):
await self._client.close()
self.changes = None

async def introspect(
self,
) -> tuple[
RawBlocksType, RawFieldsType, RawInitialValuesType, RawInitialValuesType
]:
self.changes = {}
blocks, fields, labels, initial_values = {}, [], {}, {}

blocks = {
Expand Down Expand Up @@ -81,9 +77,5 @@ async def introspect(
async def send(self, name: str, value: str):
await self._client.send(Put(name, value))

async def get_changes(self):
if self.changes is None:
raise RuntimeError("Panda not introspected.")
self.changes = (
await self._client.send(GetChanges(ChangeGroup.ALL, False))
).values
async def get_changes(self) -> dict[str, str]:
return (await self._client.send(GetChanges(ChangeGroup.ALL, False))).values
109 changes: 89 additions & 20 deletions src/fastcs_pandablocks/panda/controller.py
Original file line number Diff line number Diff line change
@@ -1,49 +1,118 @@
import asyncio

from fastcs.attributes import Attribute, AttrR, AttrRW, AttrW
from fastcs.controller import Controller
from fastcs.wrappers import scan

from fastcs_pandablocks.types import PandaName
from fastcs_pandablocks.types import (
PandaName,
RawBlocksType,
RawFieldsType,
RawInitialValuesType,
)

from .blocks import Blocks
from .client_wrapper import RawPanda
from .fields import FieldController


def _parse_introspected_data(
raw_blocks: RawBlocksType,
raw_field_infos: RawFieldsType,
raw_labels: RawInitialValuesType,
raw_initial_values: RawInitialValuesType,
):
block_controllers: dict[PandaName, FieldController] = {}
for (block_name, block_info), field_info in zip(
raw_blocks.items(), raw_field_infos, strict=True
):
numbered_block_names = (
[block_name]
if block_info.number in (None, 1)
else [
block_name + PandaName(block_number=number)
for number in range(1, block_info.number + 1)
]
)
for numbered_block_name in numbered_block_names:
block_initial_values = {
key: value
for key, value in raw_initial_values.items()
if key in numbered_block_name
}
label = raw_labels.get(numbered_block_name, None)
block = FieldController(
numbered_block_name,
label=block_info.description or label,
)
block.make_sub_fields(field_info, block_initial_values)
block_controllers[numbered_block_name] = block

return block_controllers


class PandaController(Controller):
def __init__(self, hostname: str, poll_period: float) -> None:
# TODO https://github.com/DiamondLightSource/FastCS/issues/62
self.poll_period = poll_period

self._additional_attributes: dict[str, Attribute] = {}
self._raw_panda = RawPanda(hostname)
self._blocks = Blocks()
self.is_connected = False
self._blocks: dict[PandaName, FieldController] = {}

super().__init__()

async def connect(self) -> None:
if self.is_connected:
return
@property
def additional_attributes(self):
return self._additional_attributes

async def connect(self) -> None:
await self._raw_panda.connect()
blocks, fields, labels, initial_values = await self._raw_panda.introspect()

self._blocks.parse_introspected_data(blocks, fields, labels, initial_values)
self.is_connected = True
self._blocks = _parse_introspected_data(blocks, fields, labels, initial_values)

async def initialise(self) -> None:
await self.connect()
for block_name, block in self._blocks.items():
if block.top_level_attribute is not None:
self._additional_attributes[block_name.attribute_name] = (
block.top_level_attribute
)
if block.additional_attributes or block.sub_fields:
self.register_sub_controller(block_name.attribute_name, block)
await block.initialise()

def get_attribute(self, panda_name: PandaName) -> Attribute:
assert panda_name.block
block_controller = self._blocks[panda_name.up_to_block()]
if panda_name.field is None:
assert block_controller.top_level_attribute is not None
return block_controller.top_level_attribute

field_controller = block_controller.sub_fields[panda_name.up_to_field()]
if panda_name.sub_field is None:
assert field_controller.top_level_attribute is not None
return field_controller.top_level_attribute

for attr_name, controller in self._blocks.flattened_attribute_tree():
self.register_sub_controller(attr_name, controller)
controller.initialise()
sub_field_controller = field_controller.sub_fields[panda_name]
assert sub_field_controller.top_level_attribute is not None
return sub_field_controller.top_level_attribute

async def update_field_value(self, panda_name: PandaName, value: str):
attribute = self.get_attribute(panda_name)

if isinstance(attribute, AttrW):
await attribute.process(value)
elif isinstance(attribute, (AttrRW | AttrR)):
await attribute.set(value)
else:
raise RuntimeError(f"Couldn't find panda field for {panda_name}.")

# TODO https://github.com/DiamondLightSource/FastCS/issues/62
@scan(0.1)
async def update(self):
await self._raw_panda.get_changes()
assert self._raw_panda.changes
changes = await self._raw_panda.get_changes()
await asyncio.gather(
*[
self._blocks.update_field_value(
PandaName.from_string(raw_panda_name), value
)
for raw_panda_name, value in self._raw_panda.changes.items()
self.update_field_value(PandaName.from_string(raw_panda_name), value)
for raw_panda_name, value in changes.items()
]
)
Loading

0 comments on commit 030a2e1

Please sign in to comment.