Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/fastcs/demo/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,18 @@
},
{
"$ref": "#/$defs/EnumMapping"
},
{
"items": {
"type": "string"
},
"type": "array"
},
{
"items": {
"$ref": "#/$defs/EnumMapping"
},
"type": "array"
}
]
},
Expand Down
67 changes: 43 additions & 24 deletions src/fastcs/transports/epics/ca/ioc.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,19 @@ class EpicsCAIOC:
def __init__(
self,
controller_apis: list[ControllerAPI],
aliases: Mapping[str, str | EnumMapping],
aliases: Mapping[str, str | EnumMapping | list[str] | list[EnumMapping]],
):
alias_pvs = [
value if isinstance(value, str) else value.pv for value in aliases.values()
]
alias_pvs = []

for value in aliases.values():
if isinstance(value, str):
alias_pvs.append(value)
elif isinstance(value, EnumMapping):
alias_pvs.append(value.pv)
else:
alias_pvs.extend(
item if isinstance(item, str) else item.pv for item in value
)

if duplicate_aliases := [
alias for alias, count in Counter(alias_pvs).items() if count > 1
Expand Down Expand Up @@ -130,7 +138,8 @@ def _add_sub_controller_pvi_info(parent: ControllerAPI):


def _create_and_link_attribute_pvs(
root_controller_api: ControllerAPI, aliases: Mapping[str, str | EnumMapping]
root_controller_api: ControllerAPI,
aliases: Mapping[str, str | EnumMapping | list[str] | list[EnumMapping]],
) -> None:
for controller_api in root_controller_api.walk_api():
pv_prefix = pv_prefix_from_path(controller_api.path)
Expand Down Expand Up @@ -195,7 +204,7 @@ def _create_and_link_read_pv(
pv_prefix: str,
pv_name: str,
attr_name: str,
alias: str | EnumMapping | None,
alias: str | EnumMapping | list[str] | list[EnumMapping] | None,
attribute: AttrR[DType_T],
) -> None:
pv = f"{pv_prefix}:{pv_name}"
Expand All @@ -209,11 +218,14 @@ async def async_record_set(value: DType_T):

record = _make_in_record(pv, attribute)

if isinstance(alias, str):
_add_alias(record, alias, attr_name)
elif isinstance(alias, EnumMapping):
enum_attr = _get_read_enum_attr_from_type(alias)
_add_read_enum_alias(alias, attribute, enum_attr)
aliases = alias if isinstance(alias, list) else [alias]

for alias in aliases:
if isinstance(alias, str):
_add_alias(record, alias, attr_name)
elif isinstance(alias, EnumMapping):
enum_attr = _get_read_enum_attr_from_type(alias)
_add_read_enum_alias(alias, attribute, enum_attr)

_add_attr_pvi_info(record, pv_prefix, attr_name, "r")
attribute.add_on_update_callback(async_record_set)
Expand All @@ -234,7 +246,7 @@ def _create_and_link_write_pv(
pv_prefix: str,
pv_name: str,
attr_name: str,
alias: str | EnumMapping | None,
alias: str | EnumMapping | list[str] | list[EnumMapping] | None,
attribute: AttrW[DType_T],
):
pv = f"{pv_prefix}:{pv_name}"
Expand All @@ -247,18 +259,22 @@ async def on_update(value):

record = _make_out_record(pv, attribute, on_update=on_update)

if isinstance(alias, str):
_add_alias(record, alias, attr_name)
elif isinstance(alias, EnumMapping):
enum_attr = _get_write_enum_attr_from_type(alias)
_add_write_enum_alias(alias, attribute, enum_attr)
aliases = alias if isinstance(alias, list) else [alias]

for alias in aliases:
if isinstance(alias, str):
_add_alias(record, alias, attr_name)
elif isinstance(alias, EnumMapping):
enum_attr = _get_write_enum_attr_from_type(alias)
_add_write_enum_alias(alias, attribute, enum_attr)

_add_attr_pvi_info(record, pv_prefix, attr_name, "w")
_sync_setpoint(pv, attribute, record)


def _create_and_link_command_pvs(
root_controller_api: ControllerAPI, aliases: Mapping[str, str | EnumMapping]
root_controller_api: ControllerAPI,
aliases: Mapping[str, str | EnumMapping | list[str] | list[EnumMapping]],
) -> None:
for controller_api in root_controller_api.walk_api():
pv_prefix = pv_prefix_from_path(controller_api.path)
Expand All @@ -283,7 +299,7 @@ def _create_and_link_command_pv(
pv_prefix: str,
pv_name: str,
attr_name: str,
alias: str | EnumMapping | None,
alias: str | EnumMapping | list[str] | list[EnumMapping] | None,
method: Command,
) -> None:
pv = f"{pv_prefix}:{pv_name}"
Expand All @@ -301,11 +317,14 @@ async def wrapped_method(_: Any):
ONAM="Active",
)

if isinstance(alias, str):
_add_alias(record, alias, attr_name)
elif isinstance(alias, EnumMapping):
enum_attr = _get_write_enum_attr_from_type(alias)
_add_command_enum_alias(alias, method, enum_attr)
aliases = alias if isinstance(alias, list) else [alias]

for alias in aliases:
if isinstance(alias, str):
_add_alias(record, alias, attr_name)
elif isinstance(alias, EnumMapping):
enum_attr = _get_write_enum_attr_from_type(alias)
_add_command_enum_alias(alias, method, enum_attr)

_add_attr_pvi_info(record, pv_prefix, attr_name, "x")

Expand Down
5 changes: 4 additions & 1 deletion src/fastcs/transports/epics/options.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from collections.abc import Mapping
from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
Expand Down Expand Up @@ -51,7 +52,9 @@ class EpicsCAOptions:

__pydantic_config__: ClassVar[ConfigDict] = ConfigDict(extra="forbid")

aliases: dict[str, str | EnumMapping] = field(default_factory=dict)
aliases: Mapping[str, str | EnumMapping | list[str] | list[EnumMapping]] = field(
default_factory=dict
)
"""Mapping of fastcs PV names to their aliases.

Setpoint and readback PVs must be aliased separately.
Expand Down
12 changes: 12 additions & 0 deletions tests/data/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,18 @@
},
{
"$ref": "#/$defs/EnumMapping"
},
{
"items": {
"type": "string"
},
"type": "array"
},
{
"items": {
"$ref": "#/$defs/EnumMapping"
},
"type": "array"
}
]
},
Expand Down
2 changes: 1 addition & 1 deletion tests/example_softioc.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def run(id="SOFTIOC_TEST_DEVICE"):
EpicsCATransport(
epicsca=EpicsCAOptions(
aliases={
f"{id}:B": f"{id}:AliasB",
f"{id}:B": [f"{id}:AliasB1", f"{id}:AliasB2"],
f"{id}:B_RBV": f"{id}:AliasB_RBV",
f"{id}:ChildVector:0:E": EnumMapping(
pv=f"{id}:EnumAliasE", mapping={"Off": 1, "On": 2}
Expand Down
11 changes: 7 additions & 4 deletions tests/transports/epics/ca/test_softioc_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,15 @@ def test_ioc(softioc_subprocess: tuple[str, Queue]):
}

# Assert alias. Aliases do not show up in PVI structure
assert ctxt.get(f"{pv_prefix}:B") == ctxt.get(f"{pv_prefix}:AliasB") == 0
assert ctxt.get(f"{pv_prefix}:B") == ctxt.get(f"{pv_prefix}:AliasB1") == 0
ctxt.put(f"{pv_prefix}:B", 10, wait=True)
assert ctxt.get(f"{pv_prefix}:AliasB") == 10
ctxt.put(f"{pv_prefix}:AliasB", 20, wait=True)
assert ctxt.get(f"{pv_prefix}:AliasB1") == 10
assert ctxt.get(f"{pv_prefix}:AliasB2") == 10
ctxt.put(f"{pv_prefix}:AliasB1", 20, wait=True)
assert ctxt.get(f"{pv_prefix}:B") == 20
assert ctxt.get(f"{pv_prefix}:B_RBV") == ctxt.get(f"{pv_prefix}:AliasB_RBV") == 20
ctxt.put(f"{pv_prefix}:AliasB2", 30, wait=True)
assert ctxt.get(f"{pv_prefix}:B") == 30
assert ctxt.get(f"{pv_prefix}:B_RBV") == ctxt.get(f"{pv_prefix}:AliasB_RBV") == 30

# Assert enum alias. Enum aliases do not show up in PVI structure
enum_value = ctxt.get(f"{pv_prefix}:ChildVector:0:E")
Expand Down
Loading