-
Notifications
You must be signed in to change notification settings - Fork 3
Add Shutter devices to sophys-common devices
#88
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
base: main
Are you sure you want to change the base?
Changes from 11 commits
7a4946f
4e5c8a3
867f197
9cf44ce
7de535a
b0135b4
e49b4a5
88df15c
f9aa1c1
b338fe3
28726b1
5c75b25
e52a7d8
3ac7d59
f769d15
b227be8
9f659d8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,189 @@ | ||
| from ophyd import EpicsSignal, EpicsSignalRO, FormattedComponent, Device | ||
| from ophyd.pv_positioner import PVPositionerComparator | ||
| from ..utils.status import PremadeStatus | ||
| from ophyd.status import AndStatus, SubscriptionStatus | ||
|
|
||
|
|
||
| class ShutterToggle(PVPositionerComparator): | ||
| """ | ||
|
joao-biondo marked this conversation as resolved.
|
||
| Abstraction layer for shutters with one actuation PV (OPENCLOSE) and one readback PV (PG_STATUS). There's an optional parameter for a permission PV. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| prefix: str | ||
| Prefix for the shutter's PVs. | ||
|
|
||
| setpoint_suffix: str | ||
| Suffix for the actuation PV. NOTE: This should be place/location of the shutter, e.g. OEA/FOE. | ||
| The PV will be formatted as "{prefix}{setpoint_suffix}OPENCLOSE". | ||
|
|
||
| readback_suffix: str | ||
| Suffix for the readback PV, e.g. PG_STATUS | ||
|
|
||
| permission_suffix: str, optional | ||
| Permssion PV string, if it exists. | ||
|
|
||
| NOTE | ||
| ---- | ||
| This implemantation considers that the value of the `readback` signal is 0 for an open shutter and 1 for a closed shutter. | ||
| This is not so intuitive, so the `set` method considers that 1 is for opennig and 0 for closing the shutter. | ||
|
|
||
| Usage Example | ||
| ------------- | ||
| >>> shutter = ShutterOpenClose(prefix="prefix", setpoint_suffix="setpoint_suffix", readback="readback_suffix", name="shutter") | ||
| >>> shutter.set(0).wait() # for closing | ||
| >>> shutter.set(1).wait() # for opening | ||
| """ | ||
|
|
||
| real_setpoint = None | ||
| setpoint = FormattedComponent( | ||
| EpicsSignal, "{prefix}{setpoint_suffix}OPENCLOSE", kind="config" | ||
| ) | ||
| readback = FormattedComponent( | ||
| EpicsSignalRO, "{prefix}{readback_suffix}", kind="hinted" | ||
| ) | ||
| permission = FormattedComponent( | ||
|
joao-biondo marked this conversation as resolved.
Outdated
|
||
| EpicsSignalRO, "{permission_pv}", string=True, kind="omitted" | ||
| ) | ||
|
|
||
| def __init__( | ||
| self, | ||
| *args, | ||
| setpoint_suffix: str, | ||
| readback_suffix: str, | ||
| permission_pv: str = None, | ||
| **kwargs, | ||
| ): | ||
| self.setpoint_suffix = setpoint_suffix | ||
| self.readback_suffix = readback_suffix | ||
| self.permission_pv = permission_pv | ||
| super().__init__(*args, **kwargs) | ||
|
|
||
| def set(self, value, *args, **kwargs): | ||
| if self.permission.connected: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't we do a Besides, there should be a flag set in the ctor for when To avoid a needless connection attempt and useless network traffic, I think the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice! I was struggling to find a good way to deal with this
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I read the documentation of the |
||
| if not self.permission.get(): | ||
| raise PremadeStatus( | ||
| success=False, | ||
| exception=PermissionError( | ||
| f"Shutter open permission is denied: {self.permission.pvname} {self.permission.get()}" | ||
| ), | ||
| ) | ||
|
|
||
| if ( | ||
| value == self.readback.get() | ||
| ): # Since we're swapping the readback values (o for closing and 1 for opennig), we actuate when value == readback | ||
|
joao-biondo marked this conversation as resolved.
Outdated
|
||
| self.real_setpoint = 1 if value == 0 else 0 | ||
| return super().set(1, *args, **kwargs) | ||
| else: | ||
| return PremadeStatus(success=True) | ||
|
|
||
| def done_comparator(self, readback, setpoint): | ||
| return self.real_setpoint == readback | ||
|
|
||
|
joao-biondo marked this conversation as resolved.
|
||
|
|
||
| class ShutterOpenClose(Device): | ||
| """ | ||
| Abstraction layer for shutters with two actuation PV (OPEN and CLOSE) and two readback PV (PS_STATUS and GS_STATUS). There's an optional parameter for a permission PV. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| prefix: str | ||
| Prefix for the shutter's PVs. | ||
|
|
||
| shutter_suffix: str | ||
| Suffix for the OPEN and CLOSE PVs. NOTE: This should be place/location of the shutter, e.g. OEA/FOE. | ||
| The PVs will be formatted as "{prefix}{shutter_suffix}OPEN" and "{prefix}{shutter_suffix}CLOSE". | ||
|
|
||
| ps_suffix: str | ||
| Suffix for one readback PVs, e.g. PS_STATUS | ||
|
|
||
| gs_suffix: str | ||
| Suffix for the second readback PV, e.g. GS_STATUS | ||
|
|
||
| permission_pv: str, optional | ||
| Permssion PV string, if it exists. | ||
|
|
||
| NOTES | ||
| ----- | ||
| This implemantation considers that the value of the `readback` signal is 0 for an open shutter and 1 for a closed shutter. | ||
| This is not so intuitive, so the `set` method considers that 1 is for opennig and 0 for closing the shutter. | ||
|
joao-biondo marked this conversation as resolved.
Outdated
|
||
|
|
||
| The `return` of the `set` method is an `AndStatus` with both `readback` signals. | ||
|
|
||
| There's a `done_comparator` method that returns the state of the shutter, based in the two `readback` PVs. This method is | ||
| used as the `callback` for both `readback` signals. | ||
|
|
||
| Usage Example | ||
| ------------- | ||
| >>> shutter = ShutterToggle(prefix="prefix", open_suffix="open_suffix", close_suffix="close_suffix", ps_suffix="ps_suffix", gs_suffix="gs_suffix", name="shutter") | ||
| >>> shutter.set(0).wait() # for closing | ||
| >>> shutter.set(1).wait() # for opening | ||
| """ | ||
|
|
||
| setpoint = None | ||
| photon_status = FormattedComponent( | ||
| EpicsSignalRO, "{prefix}{ps_suffix}", kind="hinted" | ||
| ) | ||
| gamma_status = FormattedComponent( | ||
| EpicsSignalRO, "{prefix}{gs_suffix}", kind="hinted" | ||
| ) | ||
| open = FormattedComponent( | ||
| EpicsSignal, "{prefix}{shutter_suffix}OPEN", kind="config" | ||
| ) | ||
| close = FormattedComponent( | ||
| EpicsSignal, "{prefix}{shutter_suffix}CLOSE", kind="config" | ||
| ) | ||
| permission = FormattedComponent( | ||
| EpicsSignalRO, "{permission_pv}", string=True, kind="omitted" | ||
| ) | ||
|
|
||
| def __init__( | ||
| self, | ||
| *args, | ||
| shutter_suffix: str, | ||
| ps_suffix: str, | ||
| gs_suffix: str, | ||
| permission_pv: str = None, | ||
| **kwargs, | ||
| ): | ||
| self.shutter_suffix = shutter_suffix | ||
| self.ps_suffix = ps_suffix | ||
| self.gs_suffix = gs_suffix | ||
| self.permission_pv = permission_pv | ||
| super().__init__(*args, **kwargs) | ||
|
|
||
| def set(self, value, *args, **kwargs): | ||
| if self.permission.connected: | ||
| if not self.permission.get(): | ||
| raise PremadeStatus( | ||
| success=False, | ||
| exception=PermissionError( | ||
| f"Shutter open permission is denied: {self.permission.pvname} {self.permission.get()}" | ||
| ), | ||
| ) | ||
|
Comment on lines
+170
to
+171
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Felipe, from IPE, suggested adding a check to wheter the shutter is already on the desired state, before calling the
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is only necessary on the Toggle shutters and you're already doing that.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It depends on the low-level implementation. If the shutters are already closed and you try to close it anyways, will it just ignore the command, or will it move the shutter to resettle? If it's the latter, the suggestion Felipe made is a potential optimization, in this case saving at least 3 seconds of settle time.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a great a point. I'd need to check with someone more knowledgeable on the low-level implementation of these devices. I'll check on that.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @flowln, I found useful to include the check, as it can, at the very least, optimize the device. I'll check with @RafaelLyra8 , who is more experienced with this shutters, if we need a |
||
|
|
||
| if value == 0: | ||
| self.close.set(1, *args, **kwargs).wait() | ||
|
|
||
| elif value == 1: | ||
| self.open.set(1, *args, **kwargs).wait() | ||
|
|
||
| else: | ||
| raise PremadeStatus( | ||
| success=False, | ||
| exception=Exception(f"The value {value} is not a valid option!"), | ||
| ) | ||
|
|
||
| self.setpoint = value | ||
|
|
||
| return AndStatus( | ||
| SubscriptionStatus(self.photon_status, self.done_comparator, settle_time=3), | ||
| SubscriptionStatus(self.gamma_status, self.done_comparator, settle_time=3), | ||
| timeout=15, | ||
| ) | ||
|
|
||
| def done_comparator(self, value, **kwargs): | ||
| is_closed = ( | ||
| self.photon_status.get() == 1 and self.gamma_status.get() == 1 | ||
| ) # NOTE: if one of the status is equal to zero, the shutter can be partially open | ||
| return is_closed if self.setpoint == 0 else not is_closed | ||
Uh oh!
There was an error while loading. Please reload this page.