Skip to content

Commit

Permalink
changed field object getter to a match statement
Browse files Browse the repository at this point in the history
  • Loading branch information
evalott100 committed Oct 29, 2024
1 parent 2424144 commit 85ab10f
Show file tree
Hide file tree
Showing 3 changed files with 196 additions and 48 deletions.
38 changes: 36 additions & 2 deletions src/fastcs_pandablocks/handlers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Any

from fastcs.attributes import Attribute, AttrW, Sender
from fastcs.attributes import Attribute, AttrR, AttrW, Handler, Sender, Updater

from fastcs_pandablocks.types import PandaName

Expand All @@ -13,11 +13,45 @@ async def put(self, controller: Any, attr: AttrW, value: str) -> None:
await controller.put_value_to_panda(self.panda_name, value)


class UpdateEguSender(Sender):
class DefaultFieldUpdater(Updater):
#: We update the fields from the top level
update_period = float("inf")

def __init__(self, panda_name: PandaName):
self.panda_name = panda_name

async def update(self, controller: Any, attr: AttrR) -> None:
pass # TODO: update the attr with the value from the panda


class DefaultFieldHandler(DefaultFieldSender, DefaultFieldUpdater, Handler):
def __init__(self, panda_name: PandaName):
super().__init__(panda_name)


class EguSender(Sender):
def __init__(self, attr_to_update: Attribute):
"""Update the attr"""
self.attr_to_update = attr_to_update

async def put(self, controller: Any, attr: AttrW, value: str) -> None:
# TODO find out how to update attr_to_update's EGU with the value
...


class CaptureHandler(Handler):
update_period = float("inf")

def __init__(self, *args, **kwargs):
pass

async def update(self, controller: Any, attr: AttrR) -> None: ...
async def put(self, controller: Any, attr: AttrW, value: Any) -> None: ...


class DatasetHandler(Handler):
update_period = float("inf")

def __init__(self, *args, **kwargs): ... # TODO: work dataset
async def update(self, controller: Any, attr: AttrR) -> None: ...
async def put(self, controller: Any, attr: AttrW, value: Any) -> None: ...
4 changes: 2 additions & 2 deletions src/fastcs_pandablocks/panda/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ def __init__(
self.fields = {}

for field_raw_name, field_info in raw_fields.items():
field_panda_name = PandaName(field=field_raw_name)
field = get_field_controller_from_field_info(field_info)
field_panda_name = self.panda_name + PandaName(field=field_raw_name)
field = get_field_controller_from_field_info(field_panda_name, field_info)
self.fields[field_panda_name.attribute_name] = field

super().__init__()
Expand Down
202 changes: 158 additions & 44 deletions src/fastcs_pandablocks/panda/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,16 @@
UintFieldInfo,
)

from fastcs_pandablocks.handlers import UpdateEguSender
from fastcs_pandablocks.handlers import (
CaptureHandler,
DatasetHandler,
DefaultFieldHandler,
DefaultFieldSender,
DefaultFieldUpdater,
EguSender,
)
from fastcs_pandablocks.types.annotations import ResponseType
from fastcs_pandablocks.types.string_types import PandaName


class WidgetGroup(Enum):
Expand All @@ -33,6 +41,17 @@ class WidgetGroup(Enum):
OUTPUTS = "Outputs"
INPUTS = "Inputs"
READBACKS = "Readbacks"
CAPTURE = "Capture"


# EPICS hardcoded. TODO: remove once we switch to pvxs.
MAXIMUM_DESCRIPTION_LENGTH = 40


def _strip_description(description: str | None) -> str:
if description is None:
return ""
return description[:MAXIMUM_DESCRIPTION_LENGTH]


class FieldController(SubController):
Expand All @@ -57,111 +76,206 @@ def initialise(self):


class TableFieldController(FieldController):
def __init__(self, table_field_info: TableFieldInfo):
def __init__(self, panda_name: PandaName, field_info: TableFieldInfo):
super().__init__()

self.top_level_attribute = AttrR(
Float(),
# To be added once we have a pvxs backend
# description=table_field_info.description,
description=_strip_description(field_info.description),
group=WidgetGroup.OUTPUTS.value,
)


class TimeFieldController(FieldController):
def __init__(self, time_field_info: TimeFieldInfo):
class TimeParamFieldController(FieldController):
# TODO: these `FieldInfo` are the exact same in pandablocks-client.
def __init__(
self,
panda_name: PandaName,
field_info: SubtypeTimeFieldInfo | TimeFieldInfo,
):
super().__init__()
self.top_level_attribute = AttrRW(
Float(),
# To be added once we have a pvxs backend
# description=time_field_info.description,
handler=DefaultFieldHandler(panda_name),
description=_strip_description(field_info.description),
group=WidgetGroup.PARAMETERS.value,
)
self._additional_attributes["units"] = AttrW(
String(),
handler=UpdateEguSender(self.top_level_attribute),
handler=EguSender(self.top_level_attribute),
group=WidgetGroup.PARAMETERS.value,
allowed_values=field_info.units_labels,
)


class TimeSubTypeParamFieldController(FieldController):
class TimeReadFieldController(FieldController):
def __init__(
self,
time_subtype_param_field_info: SubtypeTimeFieldInfo,
):
super().__init__()
self.top_level_attribute = AttrRW(
Float(),
group=WidgetGroup.PARAMETERS.value,
)


class TimeSubTypeReadFieldController(FieldController):
def __init__(
self,
time_subtype_read_field_info: SubtypeTimeFieldInfo,
panda_name: PandaName,
field_info: SubtypeTimeFieldInfo,
):
super().__init__()
self.top_level_attribute = AttrR(
Float(),
group=WidgetGroup.READBACKS.value,
handler=DefaultFieldUpdater(
panda_name=panda_name,
),
description=_strip_description(field_info.description),
group=WidgetGroup.OUTPUTS.value,
)
self._additional_attributes["units"] = AttrW(
String(),
handler=EguSender(self.top_level_attribute),
group=WidgetGroup.OUTPUTS.value,
allowed_values=field_info.units_labels,
)


class TimeSubTypeWriteFieldController(FieldController):
class TimeWriteFieldController(FieldController):
def __init__(
self,
time_subtype_write_field_info: SubtypeTimeFieldInfo,
panda_name: PandaName,
field_info: SubtypeTimeFieldInfo,
):
super().__init__()
self.top_level_attribute = AttrW(
Float(),
handler=DefaultFieldSender(panda_name),
description=_strip_description(field_info.description),
group=WidgetGroup.OUTPUTS.value,
)
self._additional_attributes["units"] = AttrW(
String(),
handler=EguSender(self.top_level_attribute),
group=WidgetGroup.READBACKS.value,
allowed_values=field_info.units_labels,
)


class BitOutFieldController(FieldController):
def __init__(self, bit_out_field_info: BitOutFieldInfo):
def __init__(self, field_info: BitOutFieldInfo):
super().__init__()
self.top_level_attribute = AttrRW(
self.top_level_attribute = AttrR(
Bool(znam="0", onam="1"),
description=_strip_description(field_info.description),
group=WidgetGroup.OUTPUTS.value,
)


class PosOutFieldController(FieldController):
def __init__(self, pos_out_field_info: PosOutFieldInfo):
def __init__(self, panda_name: PandaName, field_info: PosOutFieldInfo):
super().__init__()
self.top_level_attribute = AttrR(
top_level_attribute = AttrR(
Float(),
description=_strip_description(field_info.description),
group=WidgetGroup.OUTPUTS.value,
)

scaled = AttrR(
Float(),
group=WidgetGroup.CAPTURE.value,
description="Value with scaling applied.",
)

scale = AttrRW(
Float(),
group=WidgetGroup.CAPTURE.value,
handler=DefaultFieldHandler(panda_name),
)
offset = AttrRW(
Float(),
group=WidgetGroup.CAPTURE.value,
handler=DefaultFieldHandler(panda_name),
)

async def updated_scaled_on_offset_change(*_):
await scaled.set(scale.get() * top_level_attribute.get() + offset.get())

offset.set_update_callback(updated_scaled_on_offset_change)

self._additional_attributes.update(
{"scaled": scaled, "scale": scale, "offset": offset}
)

self.top_level_attribute = top_level_attribute
self._additional_attributes["capture"] = AttrRW(
String(),
group=WidgetGroup.CAPTURE.value,
handler=CaptureHandler(),
allowed_values=field_info.capture_labels,
)
self._additional_attributes["dataset"] = AttrRW(
String(),
group=WidgetGroup.CAPTURE.value,
handler=DatasetHandler(),
allowed_values=field_info.capture_labels,
)


class ExtOutFieldController(FieldController):
def __init__(self, ext_out_field_info: ExtOutFieldInfo):
def __init__(self, field_info: ExtOutFieldInfo):
super().__init__()
self.top_level_attribute = AttrR(
Float(),
description=_strip_description(field_info.description),
group=WidgetGroup.OUTPUTS.value,
)
self._additional_attributes["capture"] = AttrRW(
String(),
group=WidgetGroup.CAPTURE.value,
handler=CaptureHandler(),
allowed_values=field_info.capture_labels,
)
self._additional_attributes["dataset"] = AttrRW(
String(),
group=WidgetGroup.CAPTURE.value,
handler=DatasetHandler(),
allowed_values=field_info.capture_labels,
)


class ExtOutBitsFieldController(ExtOutFieldController):
def __init__(
self,
ext_out_bits_field_info: ExtOutBitsFieldInfo,
field_info: ExtOutBitsFieldInfo,
):
super().__init__(ext_out_bits_field_info)
super().__init__(field_info)

for bit_number, label in enumerate(field_info.bits, start=1):
if label == "":
continue # Some rows are empty, do not create records.

self._additional_attributes[f"val{bit_number}"] = AttrR(
Bool(znam="0", onam="1"),
description="Value of the field connected to this bit.",
group=WidgetGroup.OUTPUTS.value,
)
self._additional_attributes[f"name{bit_number}"] = AttrR(
Bool(znam="0", onam="1"),
description="Value of the field connected to this bit.",
group=WidgetGroup.OUTPUTS.value,
)


class BitMuxFieldController(FieldController):
def __init__(self, bit_mux_field_info: BitMuxFieldInfo):
def __init__(self, panda_name: PandaName, bit_mux_field_info: BitMuxFieldInfo):
super().__init__()
self.top_level_attribute = AttrRW(
String(),
description=_strip_description(bit_mux_field_info.description),
handler=DefaultFieldHandler(panda_name),
group=WidgetGroup.INPUTS.value,
)

self._additional_attributes["delay"] = AttrRW(
Float(),
description="Clock delay on input.",
handler=DefaultFieldHandler(panda_name),
group=WidgetGroup.INPUTS.value,
)

# TODO: Add DRVL DRVH to `delay`.


class PosMuxFieldController(FieldController):
def __init__(self, pos_mux_field_info: PosMuxFieldInfo):
Expand Down Expand Up @@ -357,7 +471,6 @@ def __init__(self, enum_write_field_info: EnumFieldInfo):

FieldControllerType = (
TableFieldController
| TimeFieldController
| BitOutFieldController
| PosOutFieldController
| ExtOutFieldController
Expand All @@ -384,27 +497,28 @@ def __init__(self, enum_write_field_info: EnumFieldInfo):
| EnumParamFieldController
| EnumReadFieldController
| EnumWriteFieldController
| TimeSubTypeParamFieldController
| TimeSubTypeReadFieldController
| TimeSubTypeWriteFieldController
| TimeParamFieldController
| TimeReadFieldController
| TimeWriteFieldController
)


def get_field_controller_from_field_info(
panda_name: PandaName,
field_info: ResponseType,
) -> FieldControllerType:
match field_info:
case TableFieldInfo():
return TableFieldController(field_info)
return TableFieldController(panda_name, field_info)
# Time types
case TimeFieldInfo(subtype=None):
return TimeFieldController(field_info)
return TimeParamFieldController(panda_name, field_info)
case SubtypeTimeFieldInfo(type="param"):
return TimeSubTypeParamFieldController(field_info)
return TimeParamFieldController(panda_name, field_info)
case SubtypeTimeFieldInfo(subtype="read"):
return TimeSubTypeReadFieldController(field_info)
return TimeReadFieldController(panda_name, field_info)
case SubtypeTimeFieldInfo(subtype="write"):
return TimeSubTypeWriteFieldController(field_info)
return TimeWriteFieldController(panda_name, field_info)

# Bit types
case BitOutFieldInfo():
Expand All @@ -426,7 +540,7 @@ def get_field_controller_from_field_info(

# Pos types
case PosOutFieldInfo():
return PosOutFieldController(field_info)
return PosOutFieldController(panda_name, field_info)
case PosMuxFieldInfo():
return PosMuxFieldController(field_info)

Expand Down

0 comments on commit 85ab10f

Please sign in to comment.