Skip to content
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

redo the i18 add #1030

Merged
merged 9 commits into from
Feb 3, 2025
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
124 changes: 124 additions & 0 deletions src/dodal/beamlines/i18.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
from pathlib import Path

from ophyd_async.fastcs.panda import HDFPanda

from dodal.common.beamlines.beamline_utils import (
device_factory,
get_path_provider,
set_path_provider,
)
from dodal.common.beamlines.beamline_utils import set_beamline as set_utils_beamline
from dodal.common.visit import (
LocalDirectoryServiceClient,
StaticVisitPathProvider,
)
from dodal.devices.dcm import DCM
from dodal.devices.i18.diode import Diode
from dodal.devices.i18.KBMirror import KBMirror
from dodal.devices.i18.table import Table
from dodal.devices.i18.thor_labs_stage import ThorLabsStage
from dodal.devices.slits import Slits
from dodal.devices.synchrotron import Synchrotron
from dodal.devices.tetramm import TetrammDetector
from dodal.devices.undulator import Undulator
from dodal.log import set_beamline as set_log_beamline
from dodal.utils import BeamlinePrefix, get_beamline_name

BL = get_beamline_name("i18")
PREFIX = BeamlinePrefix(BL)
set_log_beamline(BL)
set_utils_beamline(BL)


# Currently we must hard-code the visit, determining the visit at runtime requires
# infrastructure that is still WIP.
# Communication with GDA is also WIP so for now we determine an arbitrary scan number
# locally and write the commissioning directory. The scan number is not guaranteed to
# be unique and the data is at risk - this configuration is for testing only.
set_path_provider(
StaticVisitPathProvider(
BL,
Path("/dls/i18/data/2024/cm37264-2/bluesky"),
client=LocalDirectoryServiceClient(),
)
)


@device_factory()
def synchrotron() -> Synchrotron:
return Synchrotron()


@device_factory()
def undulator() -> Undulator:
return Undulator(f"{PREFIX.insertion_prefix}-MO-SERVC-01:")


@device_factory()
def dcm() -> DCM:
# once spacing is added Si111 d-spacing is 3.135 angsterm , and Si311 is 1.637
# calculations are in gda/config/lookupTables/Si111/eV_Deg_converter.xml
return DCM(
prefix=f"{PREFIX.beamline_prefix}-MO-DCM-01:",
)


@device_factory()
def slits_1() -> Slits:
return Slits(
f"{PREFIX.beamline_prefix}-AL-SLITS-01:",
x_centre="X:CENTER",
y_centre="Y:CENTER",
)


# PandA IOC needs to be updated to support PVI
@device_factory(skip=True)
def panda1() -> HDFPanda:
return HDFPanda(
f"{PREFIX.beamline_prefix}-MO-PANDA-01:",
path_provider=get_path_provider(),
)


@device_factory()
def i0() -> TetrammDetector:
return TetrammDetector(
f"{PREFIX.beamline_prefix}-DI-XBPM-02:",
path_provider=get_path_provider(),
type="Cividec Diamond XBPM",
)


@device_factory()
def it() -> TetrammDetector:
return TetrammDetector(
f"{PREFIX.beamline_prefix}-DI-XBPM-01:",
path_provider=get_path_provider(),
)


@device_factory(skip=True)
# VFM uses different IOC than HFM https://github.com/DiamondLightSource/dodal/issues/1009
def vfm() -> KBMirror:
return KBMirror(f"{PREFIX.beamline_prefix}-OP-VFM-01:")


@device_factory()
def hfm() -> KBMirror:
return KBMirror(f"{PREFIX.beamline_prefix}-OP-HFM-01:")


@device_factory()
def d7diode() -> Diode:
return Diode(f"{PREFIX.beamline_prefix}-DI-PHDGN-07:")


@device_factory()
def main_table() -> Table:
return Table(f"{PREFIX.beamline_prefix}-MO-TABLE-01:")


@device_factory()
def thor_labs_stage() -> ThorLabsStage:
return ThorLabsStage(f"{PREFIX.beamline_prefix}-MO-TABLE-02:")
19 changes: 19 additions & 0 deletions src/dodal/devices/i18/KBMirror.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from ophyd_async.core import StandardReadable
from ophyd_async.epics.core import epics_signal_rw


class KBMirror(StandardReadable):
def __init__(
self,
prefix: str,
name: str = "",
):
self._prefix = prefix
with self.add_children_as_readables():
self.x = epics_signal_rw(float, prefix + "X")
self.y = epics_signal_rw(float, prefix + "Y")
self.bend1 = epics_signal_rw(float, prefix + "BEND1")
self.bend2 = epics_signal_rw(float, prefix + "BEND2")
self.curve = epics_signal_rw(float, prefix + "CURVE")
self.ellip = epics_signal_rw(float, prefix + "ELLIP")
super().__init__(name=name)
17 changes: 17 additions & 0 deletions src/dodal/devices/i18/diode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from ophyd_async.core import (
StandardReadable,
)
from ophyd_async.epics.core import epics_signal_r


class Diode(StandardReadable):
def __init__(
self,
prefix: str,
name: str = "",
):
self._prefix = prefix
with self.add_children_as_readables():
self.signal = epics_signal_r(float, prefix + "B:DIODE:I")

super().__init__(name=name)
14 changes: 14 additions & 0 deletions src/dodal/devices/i18/table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from ophyd_async.core import (
StandardReadable,
)
from ophyd_async.epics.motor import Motor


class Table(StandardReadable):
def __init__(self, prefix: str, name: str = "") -> None:
with self.add_children_as_readables():
self.x = Motor(prefix + "X")
self.y = Motor(prefix + "Y")
self.z = Motor(prefix + "Z")
self.theta = Motor(prefix + "THETA")
super().__init__(name=name)
12 changes: 12 additions & 0 deletions src/dodal/devices/i18/thor_labs_stage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from ophyd_async.core import (
StandardReadable,
)
from ophyd_async.epics.motor import Motor


class ThorLabsStage(StandardReadable):
def __init__(self, prefix: str, name: str = "") -> None:
with self.add_children_as_readables():
self.x = Motor(prefix + "X")
self.y = Motor(prefix + "Y")
super().__init__(name=name)
64 changes: 64 additions & 0 deletions tests/devices/i18/test_kb_mirror.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from unittest.mock import ANY

import pytest
from ophyd_async.core import DeviceCollector
from ophyd_async.testing import set_mock_value

from dodal.devices.i18.KBMirror import KBMirror


@pytest.fixture
async def kbmirror() -> KBMirror:
"""Fixture to set up a mock KBMirror device using DeviceCollector."""
async with DeviceCollector(mock=True):
kbmirror = KBMirror(prefix="MIRROR:")
return kbmirror


async def test_setting_xy_position_kbmirror(kbmirror: KBMirror):
"""
Test setting x and y positions on the KBMirror using the ophyd_async mock tools.
"""
# Mock the initial values of the x and y signals
set_mock_value(kbmirror.x, 0.0)
set_mock_value(kbmirror.y, 0.0)
Comment on lines +22 to +24
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should: 0 is the default value for a float signal, so this shouldn't be required.

Suggested change
# Mock the initial values of the x and y signals
set_mock_value(kbmirror.x, 0.0)
set_mock_value(kbmirror.y, 0.0)


# Call set to update the position
await kbmirror.x.set(1.23)
await kbmirror.y.set(4.56)

reading = await kbmirror.read()
expected_reading = {
"kbmirror-y": {
"value": 4.56,
"timestamp": ANY,
"alarm_severity": 0,
},
"kbmirror-bend1": {
"value": 0.0,
"timestamp": ANY,
"alarm_severity": 0,
},
"kbmirror-ellip": {
"value": 0.0,
"timestamp": ANY,
"alarm_severity": 0,
},
"kbmirror-x": {
"value": 1.23,
"timestamp": ANY,
"alarm_severity": 0,
},
"kbmirror-bend2": {
"value": 0.0,
"timestamp": ANY,
"alarm_severity": 0,
},
"kbmirror-curve": {
"value": 0.0,
"timestamp": ANY,
"alarm_severity": 0,
},
}

assert reading == expected_reading
124 changes: 124 additions & 0 deletions tests/devices/i18/test_table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
from unittest.mock import ANY

import pytest
from ophyd_async.core import DeviceCollector
from ophyd_async.testing import set_mock_value

from dodal.devices.i18.table import Table


@pytest.fixture
async def table() -> Table:
"""Fixture to set up a mock Table device using DeviceCollector."""
async with DeviceCollector(mock=True):
table = Table(prefix="MIRROR:")
return table


async def test_setting_xy_position_table(table: Table):
"""
Test setting x and y positions on the Table using the ophyd_async mock tools.
"""

reading = await table.read()
expected_reading = {
"table-y": {
"value": 0.0,
"timestamp": ANY,
"alarm_severity": 0,
},
"table-x": {
"value": 0.0,
"timestamp": ANY,
"alarm_severity": 0,
},
"table-theta": {
"alarm_severity": 0,
"timestamp": ANY,
"value": 0.0,
},
"table-z": {"alarm_severity": 0, "timestamp": ANY, "value": 0.0},
}

assert reading == expected_reading

# Call set to update the position
set_mock_value(table.x.user_readback, 1.23)
set_mock_value(table.y.user_readback, 4.56)

reading = await table.read()
expected_reading = {
"table-y": {
"value": 4.56,
"timestamp": ANY,
"alarm_severity": 0,
},
"table-x": {
"value": 1.23,
"timestamp": ANY,
"alarm_severity": 0,
},
"table-theta": {
"alarm_severity": 0,
"timestamp": ANY,
"value": 0.0,
},
"table-z": {"alarm_severity": 0, "timestamp": ANY, "value": 0.0},
}
Comment on lines +62 to +67
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"alarm_severity": 0,
"timestamp": ANY,
"value": 0.0,
},
"table-z": {"alarm_severity": 0, "timestamp": ANY, "value": 0.0},
}
"value": 0.0,
"timestamp": ANY,
"alarm_severity": 0,
},
"table-z": {
"value": 0.0,
"timestamp": ANY,
"alarm_severity": 0,
},
}

nit: consistency with above


assert reading == expected_reading


async def test_setting_xyztheta_position_table(table: Table):
"""
Test setting x and y positions on the Table using the ophyd_async mock tools.
"""
reading = await table.read()
expected_reading = {
"table-y": {
"value": 0.0,
"timestamp": ANY,
"alarm_severity": 0,
},
"table-x": {
"value": 0.0,
"timestamp": ANY,
"alarm_severity": 0,
},
"table-theta": {
"alarm_severity": 0,
"timestamp": ANY,
"value": 0.0,
},
"table-z": {"alarm_severity": 0, "timestamp": ANY, "value": 0.0},
}

assert reading == expected_reading

# Call set to update the position
set_mock_value(table.x.user_readback, 1.23)
set_mock_value(table.y.user_readback, 4.56)
set_mock_value(table.z.user_readback, 7.89)
set_mock_value(table.theta.user_readback, 10.11)

reading = await table.read()
expected_reading = {
"table-y": {
"value": 4.56,
"timestamp": ANY,
"alarm_severity": 0,
},
"table-x": {
"value": 1.23,
"timestamp": ANY,
"alarm_severity": 0,
},
"table-theta": {
"alarm_severity": 0,
"timestamp": ANY,
"value": 10.11,
},
"table-z": {"alarm_severity": 0, "timestamp": ANY, "value": 7.89},
Comment on lines +116 to +121
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: make this consistent.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you mean?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make the formatting of "table-z" the same as the formatting of "table-theta"

}

assert reading == expected_reading
Loading
Loading