-
Notifications
You must be signed in to change notification settings - Fork 6
Split epics make record #319
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
898eb45
08bfce5
00a518c
7e13aba
37c79df
79e96b2
333dfde
39c7deb
f503e60
ee109fb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,18 +7,19 @@ | |
| from softioc.pythonSoftIoc import RecordWrapper | ||
|
|
||
| from fastcs.attributes import AttrR, AttrRW, AttrW | ||
| from fastcs.datatypes import DataType, DType_T | ||
| from fastcs.datatypes.waveform import Waveform | ||
| from fastcs.datatypes import Bool, DataType, DType_T, Enum, Float, Int, String, Waveform | ||
| from fastcs.exceptions import FastCSError | ||
| from fastcs.logging import bind_logger | ||
| from fastcs.methods import Command | ||
| from fastcs.tracer import Tracer | ||
| from fastcs.transports.controller_api import ControllerAPI | ||
| from fastcs.transports.epics import EpicsIOCOptions | ||
| from fastcs.transports.epics.ca.util import ( | ||
| builder_callable_from_attribute, | ||
| DEFAULT_STRING_WAVEFORM_LENGTH, | ||
| MBB_MAX_CHOICES, | ||
| cast_from_epics_type, | ||
| cast_to_epics_type, | ||
| record_metadata_from_attribute, | ||
| create_state_keys, | ||
| record_metadata_from_datatype, | ||
| ) | ||
| from fastcs.transports.epics.util import controller_pv_prefix | ||
|
|
@@ -187,36 +188,168 @@ async def async_record_set(value: DType_T): | |
|
|
||
| record.set(cast_to_epics_type(attribute.datatype, value)) | ||
|
|
||
| record = _make_record(pv, attribute) | ||
| record = _make_in_record(pv, attribute) | ||
| _add_attr_pvi_info(record, pv_prefix, attr_name, "r") | ||
|
|
||
| attribute.add_on_update_callback(async_record_set) | ||
|
|
||
|
|
||
| def _make_record( | ||
| def _make_in_record(pv: str, attribute: AttrR) -> RecordWrapper: | ||
| attribute_record_metadata = { | ||
| "DESC": attribute.description, | ||
| "initial_value": cast_to_epics_type(attribute.datatype, attribute.get()), | ||
| } | ||
|
|
||
| match attribute.datatype: | ||
| case Bool(): | ||
| record = builder.boolIn( | ||
| pv, ZNAM="False", ONAM="True", **attribute_record_metadata | ||
| ) | ||
| case Int(): | ||
| record = builder.longIn( | ||
| pv, | ||
| LOPR=attribute.datatype.min_alarm, | ||
| HOPR=attribute.datatype.max_alarm, | ||
| EGU=attribute.datatype.units, | ||
| **attribute_record_metadata, | ||
| ) | ||
| case Float(): | ||
| record = builder.aIn( | ||
| pv, | ||
| LOPR=attribute.datatype.min_alarm, | ||
| HOPR=attribute.datatype.max_alarm, | ||
| EGU=attribute.datatype.units, | ||
| PREC=attribute.datatype.prec, | ||
| **attribute_record_metadata, | ||
| ) | ||
| case String(): | ||
| record = builder.longStringIn( | ||
| pv, | ||
| length=attribute.datatype.length or DEFAULT_STRING_WAVEFORM_LENGTH, | ||
| **attribute_record_metadata, | ||
| ) | ||
| case Enum(): | ||
| if len(attribute.datatype.members) > MBB_MAX_CHOICES: | ||
| record = builder.longStringIn( | ||
| pv, | ||
| **attribute_record_metadata, | ||
| ) | ||
| else: | ||
| attribute_record_metadata.update(create_state_keys(attribute.datatype)) | ||
| record = builder.mbbIn( | ||
| pv, | ||
| **attribute_record_metadata, | ||
| ) | ||
| case Waveform(): | ||
| record = builder.WaveformIn( | ||
| pv, length=attribute.datatype.shape[0], **attribute_record_metadata | ||
| ) | ||
| case _: | ||
| raise FastCSError( | ||
| f"EPICS unsupported datatype on {attribute}: {attribute.datatype}" | ||
| ) | ||
|
|
||
| def datatype_updater(datatype: DataType): | ||
| for name, value in record_metadata_from_datatype(datatype).items(): | ||
| record.set_field(name, value) | ||
|
|
||
|
Comment on lines
+252
to
+255
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: rg -n -A40 "def record_metadata_from_datatype" src/fastcs/transports/epics/ca/util.pyRepository: DiamondLightSource/FastCS Length of output: 1651 🏁 Script executed: rg -n "def _make_in_record" -A20 src/fastcs/transports/epics/ca/ioc.pyRepository: DiamondLightSource/FastCS Length of output: 896 🏁 Script executed: rg -n "def _make_out_record" -A25 src/fastcs/transports/epics/ca/ioc.pyRepository: DiamondLightSource/FastCS Length of output: 971 🏁 Script executed: rg -n "builder_only_keys" src/fastcs/transports/epics/ca/ioc.pyRepository: DiamondLightSource/FastCS Length of output: 169 🏁 Script executed: rg -n "def _make_in_record" -A50 src/fastcs/transports/epics/ca/ioc.pyRepository: DiamondLightSource/FastCS Length of output: 2160 🏁 Script executed: rg -n -B5 -A15 "def datatype_updater" src/fastcs/transports/epics/ca/ioc.pyRepository: DiamondLightSource/FastCS Length of output: 1572 Add filtering for builder-only keys in The Apply the same filtering used in 🔧 Suggested fix def datatype_updater(datatype: DataType):
+ builder_only_keys = {"validate", "length"}
for name, value in record_metadata_from_datatype(datatype).items():
+ if name in builder_only_keys:
+ continue
record.set_field(name, value)🤖 Prompt for AI Agents |
||
| attribute.add_update_datatype_callback(datatype_updater) | ||
| return record | ||
|
|
||
|
|
||
| def _make_out_record( | ||
| pv: str, | ||
| attribute: AttrR | AttrW | AttrRW, | ||
| on_update: Callable | None = None, | ||
| out_record: bool = False, | ||
| attribute: AttrW | AttrRW, | ||
| on_update: Callable, | ||
| ) -> RecordWrapper: | ||
| builder_callable = builder_callable_from_attribute(attribute, on_update is None) | ||
| datatype_record_metadata = record_metadata_from_datatype( | ||
| attribute.datatype, out_record | ||
| ) | ||
| attribute_record_metadata = record_metadata_from_attribute(attribute) | ||
| attribute_record_metadata = { | ||
| "DESC": attribute.description, | ||
| "initial_value": cast_to_epics_type( | ||
| attribute.datatype, | ||
| attribute.get() | ||
| if isinstance(attribute, AttrR) | ||
| else attribute.datatype.initial_value, | ||
| ), | ||
| } | ||
|
|
||
| update = ( | ||
| {"on_update": on_update, "always_update": True, "blocking": True} | ||
| if on_update | ||
| else {} | ||
| ) | ||
| update = {"on_update": on_update, "always_update": True, "blocking": True} | ||
|
|
||
| match attribute.datatype: | ||
| case Bool(): | ||
| record = builder.boolOut( | ||
| pv, ZNAM="False", ONAM="True", **update, **attribute_record_metadata | ||
| ) | ||
| case Int(): | ||
| record = builder.longOut( | ||
| pv, | ||
| LOPR=attribute.datatype.min_alarm, | ||
| HOPR=attribute.datatype.max_alarm, | ||
| EGU=attribute.datatype.units, | ||
| DRVL=attribute.datatype.min, | ||
| DRVH=attribute.datatype.max, | ||
| **update, | ||
| **attribute_record_metadata, | ||
| ) | ||
| case Float(): | ||
| record = builder.aOut( | ||
| pv, | ||
| LOPR=attribute.datatype.min_alarm, | ||
| HOPR=attribute.datatype.max_alarm, | ||
| EGU=attribute.datatype.units, | ||
| PREC=attribute.datatype.prec, | ||
| DRVL=attribute.datatype.min, | ||
| DRVH=attribute.datatype.max, | ||
| **update, | ||
| **attribute_record_metadata, | ||
| ) | ||
| case String(): | ||
| record = builder.longStringOut( | ||
| pv, | ||
| length=attribute.datatype.length or DEFAULT_STRING_WAVEFORM_LENGTH, | ||
| **update, | ||
| **attribute_record_metadata, | ||
| ) | ||
| case Enum(): | ||
| if len(attribute.datatype.members) > MBB_MAX_CHOICES: | ||
| datatype: Enum = attribute.datatype | ||
|
|
||
| def _verify_in_datatype(_, value): | ||
| return value in datatype.names | ||
|
|
||
| record = builder.longStringOut( | ||
| pv, | ||
| validate=_verify_in_datatype, | ||
| **update, | ||
| **attribute_record_metadata, | ||
| ) | ||
|
|
||
| record = builder_callable( | ||
| pv, **update, **datatype_record_metadata, **attribute_record_metadata | ||
| ) | ||
| else: | ||
| attribute_record_metadata.update(create_state_keys(attribute.datatype)) | ||
| record = builder.mbbOut( | ||
| pv, | ||
| **update, | ||
| **attribute_record_metadata, | ||
| ) | ||
| case Waveform(): | ||
| record = builder.WaveformOut( | ||
| pv, | ||
| length=attribute.datatype.shape[0], | ||
| **update, | ||
| **attribute_record_metadata, | ||
| ) | ||
| case _: | ||
| raise FastCSError( | ||
| f"EPICS unsupported datatype on {attribute}: {attribute.datatype}" | ||
| ) | ||
|
|
||
| def datatype_updater(datatype: DataType): | ||
| for name, value in record_metadata_from_datatype(datatype, out_record).items(): | ||
| # Filter out keys that can't be set via set field | ||
| builder_only_keys = {"validate", "length"} | ||
| for name, value in record_metadata_from_datatype( | ||
| datatype, out_record=True | ||
| ).items(): | ||
| if name in builder_only_keys: | ||
| continue | ||
| record.set_field(name, value) | ||
|
|
||
| attribute.add_update_datatype_callback(datatype_updater) | ||
|
|
@@ -240,7 +373,7 @@ async def set_setpoint_without_process(value: DType_T): | |
|
|
||
| record.set(cast_to_epics_type(attribute.datatype, value), process=False) | ||
|
|
||
| record = _make_record(pv, attribute, on_update=on_update, out_record=True) | ||
| record = _make_out_record(pv, attribute, on_update=on_update) | ||
|
|
||
| _add_attr_pvi_info(record, pv_prefix, attr_name, "w") | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
datatype_updaterin removing the non-fields?partialthat provides the recorddatatype_updaterandrecord_metadata_from_datatype, unless it is easier for testing?record_metadata_from_datatypeeven returnvalidateandlength?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe that set_field casts the value as a string, and so calling it with validate would fail.
I would agree that record_metadata_from_datatype should therefore not return validate. Investigating length further.