Skip to content

i15-1: Add cobra and blower devices#1996

Open
jacob720 wants to merge 18 commits intomainfrom
cryst_bluesky_3_move_devices_to_safe
Open

i15-1: Add cobra and blower devices#1996
jacob720 wants to merge 18 commits intomainfrom
cryst_bluesky_3_move_devices_to_safe

Conversation

@jacob720
Copy link
Copy Markdown
Contributor

@jacob720 jacob720 commented Mar 27, 2026

Fixes #ISSUE
Required by DiamondLightSource/crystallography-bluesky#24
Adds cobra, blower and cryostream devices

Instructions to reviewer on how to test:

  1. Confirm tests pass

Checks for reviewer

  • Would the PR title make sense to a scientist on a set of release notes
  • If a new device has been added does it follow the standards
  • If changing the API for a pre-existing device, ensure that any beamlines using this device have updated their Bluesky plans accordingly
  • Have the connection tests for the relevant beamline(s) been run via dodal connect ${BEAMLINE}

@jacob720 jacob720 requested a review from a team as a code owner March 27, 2026 13:34
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 27, 2026

Codecov Report

❌ Patch coverage is 98.66667% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 99.11%. Comparing base (196af4f) to head (d0345aa).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
src/dodal/beamlines/i15_1.py 96.66% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1996      +/-   ##
==========================================
- Coverage   99.11%   99.11%   -0.01%     
==========================================
  Files         318      323       +5     
  Lines       12354    12464     +110     
==========================================
+ Hits        12245    12354     +109     
- Misses        109      110       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown
Contributor

@DominicOram DominicOram left a comment

Choose a reason for hiding this comment

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

There's a lot of repeated code in all the different TemperatureControllers, we should move more into the base class:

class TemperatureController(StandardReadable, Movable[TemperatureControllerPosition]):
    def __init__(self, config_client: ConfigClient, xpdf_parameters_path: str, prefix: str):
        self.config_client = config_client
        self.xpdf_parameters_path = xpdf_parameters_path        
        with self.add_children_as_readables():
            self.motor = Motor(prefix=prefix)
        super().__init__(prefix)

    def get_full_config():
       return self.config_client.get_file_contents(
            self.xpdf_parameters_path,
            desired_return_type=TemperatureControllersConfig,
            force_parser=TemperatureControllersConfig.from_xpdf_parameters,
        )

    @abstractmethod
    def get_config(): ...

    @property
    def _safe_position(self) -> float:
         return self.get_config().safe_position

    @property
    def _beam_position(self) -> float:
        return self.get_config().beam_position

    @AsyncStatus.wrap
    async def set(self, value: TemperatureControllerPosition):
        if value == TemperatureControllerPosition.SAFE:
            await self.motor.set(self._safe_position)
        elif value == TemperatureControllerPosition.BEAM:
            await self.motor.set(self._beam_position)

then:

class Blower(TemperatureController):
    def get_config(self) -> TemperatureControllerParams:
        return self.get_full_config().blower

It might then make more sense to just have one set of tests that confirms against the base class that the movement works for different sets of safe/in beam and then just have smaller sets of tests that confirm the child classes get the config from the right place


@devices.factory()
def blower_y() -> Motor:
def blower_y(config_client: ConfigClient) -> Blower:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should: It feels like a weird holdover from GDA to have two of these, can we just have the Z?

Comment on lines +211 to +220
def cobra(config_client: ConfigClient) -> Cobra:
return Cobra(
f"{PREFIX.beamline_prefix}-MO-TABLE-01:ENV:X",
config_client,
XPDF_PARAMETERS_FILEPATH,
)


@devices.factory()
def cryostream(config_client: ConfigClient) -> Cryostream:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should: Did we work out that these are interchangeable devices that go on the same rail? Can we add this into the comments here or in the device classes? If you're not sure can you double check with the scientists?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes they're interchangeable and on the same rail, there's a couple details about safety positions which I'll double check.

BEAM = "Beam"


class TemperatureController(StandardReadable, Movable[TemperatureControllerPosition]):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could: TemperatureController isn't a great name here because we're not doing anything with the fact they're temperature controllers. Maybe something more like InBeamOrSafePositioner? That's clunky too though but you get the idea

@jacob720 jacob720 requested a review from DominicOram March 31, 2026 12:14


@devices.factory()
def cobra(config_client: ConfigClient) -> Cobra:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since moving stuff around later makes merges difficult to follow, and existing devices should all be in alphanumeric order, could we start with these new device in a consistent position?


@devices.factory()
def blower_z() -> Motor:
def blower_z(config_client: ConfigClient) -> Blower:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I was hoping we might have some way to tell if this motor was configured in Y mode or Z mode before we added devices for both. I worry that people may rely on the metadata of which motor was moved, and then assume that the motor was moving in Y mode when it was actually configured to move in Z, which is why I made a comment rather than a duplicate device.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants