Skip to content

Commit 77a720d

Browse files
authored
Make sure initial values are respected in CA transport (#257)
1 parent afd4180 commit 77a720d

File tree

4 files changed

+129
-4
lines changed

4 files changed

+129
-4
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ dev = [
5050
"pytest-cov",
5151
"pytest-mock",
5252
"pytest-asyncio",
53+
"pytest-forked",
5354
"pytest-markdown-docs",
5455
"ruff",
5556
"sphinx-autobuild",

src/fastcs/transport/epics/ca/util.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from dataclasses import asdict
2+
from typing import Any
23

34
from softioc import builder
45

@@ -45,10 +46,19 @@
4546

4647
def record_metadata_from_attribute(
4748
attribute: Attribute[T],
48-
) -> dict[str, str | None]:
49+
) -> dict[str, Any]:
4950
"""Converts attributes on the `Attribute` to the
5051
field name/value in the record metadata."""
51-
return {"DESC": attribute.description}
52+
metadata: dict[str, Any] = {"DESC": attribute.description}
53+
initial = None
54+
match attribute:
55+
case AttrR():
56+
initial = attribute.get()
57+
case AttrW():
58+
initial = attribute.datatype.initial_value
59+
if initial is not None:
60+
metadata["initial_value"] = cast_to_epics_type(attribute.datatype, initial)
61+
return metadata
5262

5363

5464
def record_metadata_from_datatype(
@@ -120,7 +130,7 @@ def cast_from_epics_type(datatype: DataType[T], value: object) -> T:
120130
raise ValueError(f"Unsupported datatype {datatype}")
121131

122132

123-
def cast_to_epics_type(datatype: DataType[T], value: T) -> object:
133+
def cast_to_epics_type(datatype: DataType[T], value: T) -> Any:
124134
"""Casts from an attribute's datatype to an EPICS datatype."""
125135
match datatype:
126136
case Enum():
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import asyncio
2+
import enum
3+
4+
import numpy as np
5+
import pytest
6+
7+
import fastcs.transport.epics.ca.ioc as ca_ioc
8+
from fastcs.attributes import AttrR, AttrRW, AttrW
9+
from fastcs.controller import Controller
10+
from fastcs.datatypes import Bool, Enum, Float, Int, String, Waveform
11+
from fastcs.launch import FastCS
12+
from fastcs.transport.epics.ca.transport import EpicsCATransport
13+
from fastcs.transport.epics.options import EpicsIOCOptions
14+
15+
16+
class InitialEnum(enum.Enum):
17+
A = 0
18+
B = 1
19+
C = 2
20+
21+
22+
class InitialValuesController(Controller):
23+
int = AttrRW(Int(), initial_value=4)
24+
float = AttrRW(Float(), initial_value=3.1)
25+
bool = AttrRW(Bool(), initial_value=True)
26+
enum = AttrRW(Enum(InitialEnum), initial_value=InitialEnum.B)
27+
str = AttrRW(String(), initial_value="initial")
28+
waveform = AttrRW(
29+
Waveform(np.int64, shape=(10,)),
30+
initial_value=np.array(range(10), dtype=np.int64),
31+
)
32+
int_r = AttrR(Int(), initial_value=5)
33+
float_r = AttrR(Float(), initial_value=4.1)
34+
bool_r = AttrR(Bool(), initial_value=False)
35+
enum_r = AttrR(Enum(InitialEnum), initial_value=InitialEnum.C)
36+
str_r = AttrR(String(), initial_value="initial_r")
37+
waveform_r = AttrR(
38+
Waveform(np.int64, shape=(10,)),
39+
initial_value=np.array(range(10, 20), dtype=np.int64),
40+
)
41+
int_w = AttrW(Int())
42+
float_w = AttrW(Float())
43+
bool_w = AttrW(Bool())
44+
enum_w = AttrW(Enum(InitialEnum))
45+
str_w = AttrW(String())
46+
waveform_w = AttrW(Waveform(np.int64, shape=(10,)))
47+
48+
49+
@pytest.mark.forked
50+
@pytest.mark.asyncio
51+
async def test_initial_values_set_in_ca(mocker):
52+
pv_prefix = "SOFTIOC_INITIAL_DEVICE"
53+
54+
loop = asyncio.get_event_loop()
55+
controller = InitialValuesController()
56+
fastcs = FastCS(
57+
controller,
58+
[EpicsCATransport(ca_ioc=EpicsIOCOptions(pv_prefix=pv_prefix))],
59+
loop,
60+
)
61+
62+
record_spy = mocker.spy(ca_ioc, "_make_record")
63+
64+
task = asyncio.create_task(fastcs.serve(interactive=False))
65+
try:
66+
async with asyncio.timeout(3):
67+
while not record_spy.spy_return_list:
68+
await asyncio.sleep(0)
69+
70+
initial_values = {
71+
wrapper.name: wrapper.get() for wrapper in record_spy.spy_return_list
72+
}
73+
for name, value in {
74+
"SOFTIOC_INITIAL_DEVICE:Bool": 1,
75+
"SOFTIOC_INITIAL_DEVICE:BoolR": 0,
76+
"SOFTIOC_INITIAL_DEVICE:BoolW": 0,
77+
"SOFTIOC_INITIAL_DEVICE:Bool_RBV": 1,
78+
"SOFTIOC_INITIAL_DEVICE:Enum": 1,
79+
"SOFTIOC_INITIAL_DEVICE:EnumR": 2,
80+
"SOFTIOC_INITIAL_DEVICE:EnumW": 0,
81+
"SOFTIOC_INITIAL_DEVICE:Enum_RBV": 1,
82+
"SOFTIOC_INITIAL_DEVICE:Float": 3.1,
83+
"SOFTIOC_INITIAL_DEVICE:FloatR": 4.1,
84+
"SOFTIOC_INITIAL_DEVICE:FloatW": 0.0,
85+
"SOFTIOC_INITIAL_DEVICE:Float_RBV": 3.1,
86+
"SOFTIOC_INITIAL_DEVICE:Int": 4,
87+
"SOFTIOC_INITIAL_DEVICE:IntR": 5,
88+
"SOFTIOC_INITIAL_DEVICE:IntW": 0,
89+
"SOFTIOC_INITIAL_DEVICE:Int_RBV": 4,
90+
"SOFTIOC_INITIAL_DEVICE:Str": "initial",
91+
"SOFTIOC_INITIAL_DEVICE:StrR": "initial_r",
92+
"SOFTIOC_INITIAL_DEVICE:StrW": "",
93+
"SOFTIOC_INITIAL_DEVICE:Str_RBV": "initial",
94+
"SOFTIOC_INITIAL_DEVICE:Waveform": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
95+
"SOFTIOC_INITIAL_DEVICE:WaveformR": [
96+
10,
97+
11,
98+
12,
99+
13,
100+
14,
101+
15,
102+
16,
103+
17,
104+
18,
105+
19,
106+
],
107+
"SOFTIOC_INITIAL_DEVICE:WaveformW": 10 * [0],
108+
"SOFTIOC_INITIAL_DEVICE:Waveform_RBV": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
109+
}.items():
110+
assert np.array_equal(value, initial_values[name])
111+
except Exception as e:
112+
raise e
113+
finally:
114+
task.cancel()

tests/transport/epics/pva/test_p4p.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@ class SomeController(Controller):
390390
}
391391

392392

393-
def test_more_exotic_dataypes():
393+
def test_more_exotic_datatypes():
394394
table_columns: list[tuple[str, DTypeLike]] = [
395395
("A", "i"),
396396
("B", "i"),

0 commit comments

Comments
 (0)