Skip to content
Merged
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
4 changes: 2 additions & 2 deletions src/secop_ophyd/GenNodeCode.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,8 @@ def __init__(self, path: str | None = None, log=None):
self.add_import("ophyd_async.core", "SupersetEnum")
self.add_import("typing", "Any")
self.add_import("numpy", "ndarray")
self.add_import("secop_ophyd.SECoPDevices", "ParameterType")
self.add_import("secop_ophyd.SECoPDevices", "PropertyType")
self.add_import("secop_ophyd.SECoPDevices", "ParameterType as ParamT")
self.add_import("secop_ophyd.SECoPDevices", "PropertyType as PropT")
# Add necessary Device imports
self.add_import("secop_ophyd.SECoPDevices", "SECoPDevice")
self.add_import("secop_ophyd.SECoPDevices", "SECoPCommunicatorDevice")
Expand Down
4 changes: 2 additions & 2 deletions src/secop_ophyd/SECoPDevices.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ class ParameterType:

def __repr__(self) -> str:
"""Return repr suitable for code generation in annotations."""
return "ParameterType()"
return "ParamT()"

def __call__(self, parent: Device, child: Device):
if not isinstance(child, Signal):
Expand All @@ -162,7 +162,7 @@ class PropertyType:

def __repr__(self) -> str:
"""Return repr suitable for code generation in annotations."""
return "PropertyType()"
return "PropT()"

def __call__(self, parent: Device, child: Device):
if not isinstance(child, Signal):
Expand Down
154 changes: 154 additions & 0 deletions tests/test_annotation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# mypy: disable-error-code="attr-defined"


from ophyd_async.core import SignalR, init_devices

from secop_ophyd.SECoPDevices import SECoPMoveableDevice, SECoPNodeDevice


async def test_subset_signals_annotation(cryo_sim):

from typing import Annotated as A

from numpy import ndarray
from ophyd_async.core import SignalRW
from ophyd_async.core import StandardReadableFormat as Format
from ophyd_async.core import StrictEnum

from secop_ophyd.SECoPDevices import ParameterType

class Cryostat_Mode_Enum(StrictEnum):
"""mode enum for `Cryostat`."""

RAMP = "ramp"
PID = "pid"
OPENLOOP = "openloop"

class Cryostat(SECoPMoveableDevice):
"""A simulated cc cryostat with heat-load, specific heat for the sample and a
temperature dependent heat-link between sample and regulation."""

# Module Parameters
value: A[
SignalR[float], ParameterType(), Format.HINTED_SIGNAL
] # regulation temperature; Unit: (K)
status: A[SignalR[ndarray], ParameterType()] # current status of the module
target: A[
SignalRW[float], ParameterType(), Format.HINTED_SIGNAL
] # target temperature; Unit: (K)

class Cryo_7_frappy_demo(SECoPNodeDevice):
"""short description

This is a very long description providing all the gory details about the stuff
we are describing.
"""

# Module Devices
cryo: Cryostat

async with init_devices():
cryo = Cryo_7_frappy_demo(
sec_node_uri="localhost:10769",
)

val_read = await cryo.cryo.read()
assert val_read != {}


async def test_enum_annotation(cryo_sim):

from typing import Annotated as A

from numpy import ndarray
from ophyd_async.core import SignalR, SignalRW
from ophyd_async.core import StandardReadableFormat as Format
from ophyd_async.core import StrictEnum

from secop_ophyd.SECoPDevices import (
ParameterType,
PropertyType,
SECoPMoveableDevice,
SECoPNodeDevice,
)

class Cryostat_Mode_Enum(StrictEnum):
"""mode enum for `Cryostat`."""

RAMP = "ramp"
PID = "pid"
OPENLOOP = "openloop"

class Cryostat(SECoPMoveableDevice):
"""A simulated cc cryostat with heat-load, specific heat for the sample and a
temperature dependent heat-link between sample and regulation."""

# Module Properties
group: A[SignalR[str], PropertyType()]
description: A[SignalR[str], PropertyType()]
implementation: A[SignalR[str], PropertyType()]
interface_classes: A[SignalR[ndarray], PropertyType()]
features: A[SignalR[ndarray], PropertyType()]

# Module Parameters
value: A[
SignalR[float], ParameterType(), Format.HINTED_SIGNAL
] # regulation temperature; Unit: (K)
status: A[SignalR[ndarray], ParameterType()] # current status of the module
target: A[
SignalRW[float], ParameterType(), Format.HINTED_SIGNAL
] # target temperature; Unit: (K)
ramp: A[
SignalRW[float], ParameterType()
] # ramping speed of the setpoint; Unit: (K/min)
setpoint: A[
SignalR[float], ParameterType()
] # current setpoint during ramping else target; Unit: (K)
mode: A[SignalRW[Cryostat_Mode_Enum], ParameterType()] # mode of regulation
maxpower: A[SignalRW[float], ParameterType()] # Maximum heater power; Unit: (W)
heater: A[SignalR[float], ParameterType()] # current heater setting; Unit: (%)
heaterpower: A[
SignalR[float], ParameterType()
] # current heater power; Unit: (W)
pid: A[SignalRW[ndarray], ParameterType()] # regulation coefficients
p: A[
SignalRW[float], ParameterType()
] # regulation coefficient 'p'; Unit: (%/K)
i: A[SignalRW[float], ParameterType()] # regulation coefficient 'i'
d: A[SignalRW[float], ParameterType()] # regulation coefficient 'd'
tolerance: A[
SignalRW[float], ParameterType()
] # temperature range for stability checking; Unit: (K)
window: A[
SignalRW[float], ParameterType()
] # time window for stability checking; Unit: (s)
timeout: A[
SignalRW[float], ParameterType()
] # max waiting time for stabilisation check; Unit: (s)

class Cryo_7_frappy_demo(SECoPNodeDevice):
"""short description

This is a very long description providing all the gory details about the stuff
we are describing.
"""

# Module Devices
cryo: Cryostat

# Node Properties
equipment_id: A[SignalR[str], PropertyType()]
firmware: A[SignalR[str], PropertyType()]
description: A[SignalR[str], PropertyType()]
_interfaces: A[SignalR[ndarray], PropertyType()]

async with init_devices():
cryo = Cryo_7_frappy_demo(
sec_node_uri="localhost:10769",
)

await cryo.cryo.mode.set(Cryostat_Mode_Enum.RAMP)
mode_read = await cryo.cryo.mode.get_value()

print(mode_read)
assert mode_read == Cryostat_Mode_Enum.RAMP
46 changes: 23 additions & 23 deletions tests/test_classgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,11 @@ def sample_method(self, value: int) -> str:
# Verify code contains expected elements
assert "from abc import abstractmethod" in code
assert "class TestModule(SECoPDevice):" in code
assert "temperature: A[SignalR[float], ParameterType()]" in code
assert "count: A[SignalRW[int], ParameterType()]" in code
assert "temperature: A[SignalR[float], ParamT()]" in code
assert "count: A[SignalRW[int], ParamT()]" in code
assert "class TestNode(SECoPNodeDevice):" in code
assert "module1: TestModule" in code
assert "status: A[SignalR[str], PropertyType()]" in code
assert "status: A[SignalR[str], PropT()]" in code
assert "def sample_command" in code

print("\n✓ All basic tests passed!")
Expand Down Expand Up @@ -221,27 +221,27 @@ def type2_command(self, mode: str) -> str:
type="SignalR",
type_param="float",
description="this has to be in the final output",
path_annotation="ParameterType()",
path_annotation=str(ParameterType()),
),
ParameterAttribute(
name="setpoint",
type="SignalRW",
type_param="float",
path_annotation="ParameterType()",
path_annotation=str(ParameterType()),
),
],
properties=[
PropertyAttribute(
name="description",
type="SignalR",
type_param="str",
path_annotation="PropertyType()",
path_annotation=str(PropertyType()),
),
PropertyAttribute(
name="interface_classes",
type="SignalR",
type_param="int",
path_annotation="PropertyType()",
path_annotation=str(PropertyType()),
),
],
cmd_plans=[method_type1],
Expand All @@ -257,21 +257,21 @@ def type2_command(self, mode: str) -> str:
name="pressure",
type="SignalR",
type_param="float",
path_annotation="ParameterType()",
path_annotation=str(ParameterType()),
),
ParameterAttribute(
name="mode",
type="SignalRW",
type_param="str",
path_annotation="ParameterType()",
path_annotation=str(ParameterType()),
),
],
properties=[
PropertyAttribute(
name="implementation",
type="SignalR",
type_param="str",
path_annotation="PropertyType()",
path_annotation=str(PropertyType()),
),
],
cmd_plans=[method_type2],
Expand All @@ -291,7 +291,7 @@ def type2_command(self, mode: str) -> str:
name="status",
type="SignalR",
type_param="str",
path_annotation="PropertyType()",
path_annotation=str(PropertyType()),
),
],
description="NodeA with Type1 and Type2 modules",
Expand Down Expand Up @@ -336,27 +336,27 @@ def type3_command(self, count: int) -> int:
name="temperature",
type="SignalR",
type_param="float",
path_annotation="ParameterType()",
path_annotation=str(ParameterType()),
),
ParameterAttribute(
name="setpoint",
type="SignalRW",
type_param="float",
path_annotation="ParameterType()",
path_annotation=str(ParameterType()),
),
],
properties=[
PropertyAttribute(
name="description",
type="SignalR",
type_param="str",
path_annotation="PropertyType()",
path_annotation=str(PropertyType()),
),
PropertyAttribute(
name="interface_classes",
type="SignalR",
type_param="list",
path_annotation="PropertyType()",
path_annotation=str(PropertyType()),
),
],
cmd_plans=[method_type1],
Expand All @@ -373,21 +373,21 @@ def type3_command(self, count: int) -> int:
type="SignalRW",
type_param="int",
description="this is a description",
path_annotation="ParameterType()",
path_annotation=str(ParameterType()),
),
ParameterAttribute(
name="enabled",
type="SignalR",
type_param="bool",
path_annotation="ParameterType()",
path_annotation=str(ParameterType()),
),
],
properties=[
PropertyAttribute(
name="group",
type="SignalR",
type_param="str",
path_annotation="PropertyType()",
path_annotation=str(PropertyType()),
),
],
cmd_plans=[method_type3],
Expand All @@ -407,7 +407,7 @@ def type3_command(self, count: int) -> int:
name="name",
type="SignalR",
type_param="str",
path_annotation="PropertyType()",
path_annotation=str(PropertyType()),
),
],
description="NodeB with Type1 and Type3 modules",
Expand Down Expand Up @@ -654,14 +654,14 @@ def test_gen_shall_mass_spec_node(
assert "\n# ; Unit: (%)" not in generated_code

# Intentionally multiline descriptions should be rendered as multiline comments
assert "mid_descriptor: A[SignalRW[ndarray], ParameterType()]" in generated_code
assert "mid_descriptor: A[SignalRW[ndarray], ParamT()]" in generated_code
assert "# Example:" in generated_code
assert "# {" in generated_code
assert "# mass: [12,15,28,75]," in generated_code
assert "# device: [FARADAY,SEM,SEM,SEM]" in generated_code

# Long descriptions should be rendered fully below the declaration and wrapped
assert "resolution: A[SignalR[float], ParameterType()]\n" in generated_code
assert "resolution: A[SignalR[float], ParamT()]\n" in generated_code
assert (
"# The high mass peak width/valley adjustment used during set up and"
in generated_code
Expand All @@ -675,10 +675,10 @@ def test_gen_shall_mass_spec_node(
roundtrip_gen = GenNodeCode(path=str(clean_generated_file))
roundtrip_code = roundtrip_gen.generate_code()

assert "mid_descriptor: A[SignalRW[ndarray], ParameterType()]" in roundtrip_code
assert "mid_descriptor: A[SignalRW[ndarray], ParamT()]" in roundtrip_code
assert "Example:" in roundtrip_code
assert "\n# ; Unit: (V)" not in roundtrip_code
assert "resolution: A[SignalR[float], ParameterType()]\n" in roundtrip_code
assert "resolution: A[SignalR[float], ParamT()]\n" in roundtrip_code


def test_gen_shall_mass_spec_node_no_impl(
Expand Down
9 changes: 6 additions & 3 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.