Skip to content

Commit

Permalink
Introduced WaveForm to epics, tango, and rest
Browse files Browse the repository at this point in the history
  • Loading branch information
evalott100 committed Dec 13, 2024
1 parent 6132c36 commit 5327339
Show file tree
Hide file tree
Showing 12 changed files with 328 additions and 121 deletions.
50 changes: 48 additions & 2 deletions src/fastcs/datatypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
from functools import cached_property
from typing import Generic, TypeVar

T = TypeVar("T", int, float, bool, str, enum.IntEnum)
import numpy as np

T = TypeVar("T", int, float, bool, str, enum.IntEnum, np.ndarray)

ATTRIBUTE_TYPES: tuple[type] = T.__constraints__ # type: ignore

Expand Down Expand Up @@ -44,8 +46,9 @@ def validate(self, value: T) -> T:
return value

@property
@abstractmethod
def initial_value(self) -> T:
return self.dtype()
pass


T_Numerical = TypeVar("T_Numerical", int, float)
Expand All @@ -67,6 +70,10 @@ def validate(self, value: T_Numerical) -> T_Numerical:
raise ValueError(f"Value {value} is greater than maximum {self.max}")
return value

@property
def initial_value(self) -> T_Numerical:
return self.dtype(0)


@dataclass(frozen=True)
class Int(_Numerical[int]):
Expand Down Expand Up @@ -103,6 +110,10 @@ class Bool(DataType[bool]):
def dtype(self) -> type[bool]:
return bool

@property
def initial_value(self) -> bool:
return False


@dataclass(frozen=True)
class String(DataType[str]):
Expand All @@ -114,6 +125,10 @@ class String(DataType[str]):
def dtype(self) -> type[str]:
return str

@property
def initial_value(self) -> str:
return ""


T_Enum = TypeVar("T_Enum", bound=enum.IntEnum)

Expand Down Expand Up @@ -143,3 +158,34 @@ def dtype(self) -> type[enum.IntEnum]:
@property
def initial_value(self) -> enum.IntEnum:
return self.members[0]


@dataclass(frozen=True)
class WaveForm(DataType[np.ndarray]):
array_dtype: np.typing.DTypeLike
shape: tuple[int, ...] = (2000,)

@property
def dtype(self) -> type[np.ndarray]:
return np.ndarray

@property
def initial_value(self) -> np.ndarray:
return np.zeros(self.shape, dtype=self.array_dtype)

def validate(self, value: np.ndarray) -> np.ndarray:
super().validate(value)
if self.array_dtype != value.dtype:
raise ValueError(
f"Value dtype {value.dtype} is not the same as the array dtype "
f"{self.array_dtype}"
)
if len(self.shape) != len(value.shape) or any(
shape1 > shape2
for shape1, shape2 in zip(value.shape, self.shape, strict=True)
):
raise ValueError(
f"Value shape {value.shape} exceeeds the shape maximum shape "
f"{self.shape}"
)
return value
38 changes: 21 additions & 17 deletions src/fastcs/transport/epics/gui.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import enum

from pvi._format.dls import DLSFormatter
from pvi.device import (
Expand Down Expand Up @@ -27,7 +26,7 @@
from fastcs.attributes import Attribute, AttrR, AttrRW, AttrW
from fastcs.controller import Controller, SingleMapping, _get_single_mapping
from fastcs.cs_methods import Command
from fastcs.datatypes import Bool, Enum, Float, Int, String
from fastcs.datatypes import Bool, Enum, Float, Int, String, WaveForm
from fastcs.exceptions import FastCSException
from fastcs.util import snake_to_pascal

Expand All @@ -44,7 +43,7 @@ def _get_pv(self, attr_path: list[str], name: str):
return f"{attr_prefix}:{name.title().replace('_', '')}"

@staticmethod
def _get_read_widget(attribute: AttrR) -> ReadWidgetUnion:
def _get_read_widget(attribute: AttrR) -> ReadWidgetUnion | None:
match attribute.datatype:
case Bool():
return LED()
Expand All @@ -54,43 +53,41 @@ def _get_read_widget(attribute: AttrR) -> ReadWidgetUnion:
return TextRead(format=TextFormat.string)
case Enum():
return TextRead(format=TextFormat.string)
case WaveForm():
return None
case datatype:
raise FastCSException(f"Unsupported type {type(datatype)}: {datatype}")

@staticmethod
def _get_write_widget(attribute: AttrW) -> WriteWidgetUnion:
def _get_write_widget(attribute: AttrW) -> WriteWidgetUnion | None:
match attribute.datatype:
case Bool():
return ToggleButton()
case Int() | Float():
return TextWrite()
case String():
return TextWrite(format=TextFormat.string)
case Enum(enum_cls=enum_cls):
match enum_cls:
case enum_cls if issubclass(enum_cls, enum.Enum):
return ComboBox(
choices=[
member.name for member in attribute.datatype.members
]
)
case _:
raise FastCSException(
f"Unsupported Enum type {type(enum_cls)}: {enum_cls}"
)
case Enum():
return ComboBox(
choices=[member.name for member in attribute.datatype.members]
)
case WaveForm():
return None
case datatype:
raise FastCSException(f"Unsupported type {type(datatype)}: {datatype}")

def _get_attribute_component(
self, attr_path: list[str], name: str, attribute: Attribute
) -> SignalR | SignalW | SignalRW:
) -> SignalR | SignalW | SignalRW | None:
pv = self._get_pv(attr_path, name)
name = name.title().replace("_", "")

match attribute:
case AttrRW():
read_widget = self._get_read_widget(attribute)
write_widget = self._get_write_widget(attribute)
if write_widget is None or write_widget is None:
return None
return SignalRW(
name=name,
write_pv=pv,
Expand All @@ -100,9 +97,13 @@ def _get_attribute_component(
)
case AttrR():
read_widget = self._get_read_widget(attribute)
if read_widget is None:
return None
return SignalR(name=name, read_pv=pv, read_widget=read_widget)
case AttrW():
write_widget = self._get_write_widget(attribute)
if write_widget is None:
return None
return SignalW(name=name, write_pv=pv, write_widget=write_widget)
case _:
raise FastCSException(f"Unsupported attribute type: {type(attribute)}")
Expand Down Expand Up @@ -161,6 +162,9 @@ def extract_mapping_components(self, mapping: SingleMapping) -> Tree:
print(f"Invalid name:\n{e}")
continue

if signal is None:
continue

match attribute:
case Attribute(group=group) if group is not None:
if group not in groups:
Expand Down
123 changes: 50 additions & 73 deletions src/fastcs/transport/epics/ioc.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import warnings
from collections.abc import Callable
from types import MethodType
from typing import Any, Literal
Expand All @@ -9,7 +8,7 @@

from fastcs.attributes import AttrR, AttrRW, AttrW
from fastcs.controller import BaseController, Controller
from fastcs.datatypes import Bool, DataType, Enum, Float, Int, String, T
from fastcs.datatypes import Bool, DataType, Enum, Float, Int, String, T, WaveForm
from fastcs.exceptions import FastCSException
from fastcs.transport.epics.util import (
MBB_MAX_CHOICES,
Expand Down Expand Up @@ -200,41 +199,30 @@ def _get_input_record(pv: str, attribute: AttrR) -> RecordWrapper:
)
case Enum():
if len(attribute.datatype.members) > MBB_MAX_CHOICES:
if attribute.datatype.is_string_enum:
replacement_record, replacement_str = (
builder.longStringIn,
"longStringIn",
)
else:
replacement_record, replacement_str = builder.longIn, "longIn"

warnings.warn(
f"Received an enum datatype on attribute {attribute} "
raise RuntimeError(
f"Received an `Enum` datatype on attribute {attribute} "
f"with more elements than the epics limit `{MBB_MAX_CHOICES}` "
f"for mbbIn, will use a {replacement_str} record instead. "
"To stop with warning use a different datatype with "
"`allowed_values`",
stacklevel=1,
f"for `mbbIn`. Use an `Int or `String with `allowed_values`."
)
record = replacement_record(
pv,
**get_record_metadata_from_datatype(attribute.datatype),
**get_record_metadata_from_attribute(attribute),
)
else:
state_keys = dict(
zip(
MBB_STATE_FIELDS,
[member.name for member in attribute.datatype.members],
strict=False,
)
)
record = builder.mbbIn(
pv,
**state_keys,
**get_record_metadata_from_datatype(attribute.datatype),
**get_record_metadata_from_attribute(attribute),
state_keys = dict(
zip(
MBB_STATE_FIELDS,
[member.name for member in attribute.datatype.members],
strict=False,
)
)
record = builder.mbbIn(
pv,
**state_keys,
**get_record_metadata_from_datatype(attribute.datatype),
**get_record_metadata_from_attribute(attribute),
)
case WaveForm():
record = builder.WaveformIn(
pv,
**get_record_metadata_from_datatype(attribute.datatype),
**get_record_metadata_from_attribute(attribute),
)
case _:
raise FastCSException(
f"Unsupported type {type(attribute.datatype)}: {attribute.datatype}"
Expand Down Expand Up @@ -302,48 +290,37 @@ def _get_output_record(pv: str, attribute: AttrW, on_update: Callable) -> Any:
**get_record_metadata_from_datatype(attribute.datatype),
**get_record_metadata_from_attribute(attribute),
)
case Enum(enum_cls=enum_cls):
members = list(enum_cls)
if len(members) > MBB_MAX_CHOICES:
if attribute.datatype.is_string_enum:
replacement_record, replacement_str = (
builder.longStringOut,
"longStringOut",
)
else:
replacement_record, replacement_str = builder.longOut, "longOut"

warnings.warn(
f"Received an enum datatype on attribute {attribute} "
case Enum():
if len(attribute.datatype.members) > MBB_MAX_CHOICES:
raise RuntimeError(
f"Received an `Enum` datatype on attribute {attribute} "
f"with more elements than the epics limit `{MBB_MAX_CHOICES}` "
f"for mbbOut, will use a {replacement_str} record instead. "
"To stop with warning use a different datatype with "
"`allowed_values`",
stacklevel=1,
f"for `mbbOut`. Use an `Int or `String with `allowed_values`."
)
record = replacement_record(
pv,
always_update=True,
on_update=on_update,
**get_record_metadata_from_datatype(attribute.datatype),
**get_record_metadata_from_attribute(attribute),
)
else:
state_keys = dict(
zip(
MBB_STATE_FIELDS,
[member.name for member in members],
strict=False,
)
)
record = builder.mbbOut(
pv,
**state_keys,
always_update=True,
on_update=on_update,
**get_record_metadata_from_datatype(attribute.datatype),
**get_record_metadata_from_attribute(attribute),

state_keys = dict(
zip(
MBB_STATE_FIELDS,
[member.name for member in attribute.datatype.members],
strict=False,
)
)
record = builder.mbbOut(
pv,
**state_keys,
always_update=True,
on_update=on_update,
**get_record_metadata_from_datatype(attribute.datatype),
**get_record_metadata_from_attribute(attribute),
)
case WaveForm():
record = builder.WaveformOut(
pv,
always_update=True,
on_update=on_update,
**get_record_metadata_from_datatype(attribute.datatype),
**get_record_metadata_from_attribute(attribute),
)

case _:
raise FastCSException(
Expand Down
5 changes: 3 additions & 2 deletions src/fastcs/transport/epics/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from dataclasses import asdict

from fastcs.attributes import Attribute
from fastcs.datatypes import Bool, DataType, Enum, Float, Int, String, T
from fastcs.datatypes import Bool, DataType, Enum, Float, Int, String, T, WaveForm

_MBB_FIELD_PREFIXES = (
"ZR",
Expand All @@ -28,7 +28,7 @@
MBB_MAX_CHOICES = len(_MBB_FIELD_PREFIXES)


EPICS_ALLOWED_DATATYPES = (Bool, DataType, Enum, Float, Int, String)
EPICS_ALLOWED_DATATYPES = (Bool, DataType, Enum, Float, Int, String, WaveForm)

DATATYPE_FIELD_TO_RECORD_FIELD = {
"prec": "PREC",
Expand All @@ -39,6 +39,7 @@
"max_alarm": "HOPR",
"znam": "ZNAM",
"onam": "ONAM",
"shape": "length",
}


Expand Down
Loading

0 comments on commit 5327339

Please sign in to comment.