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

SmarAct Encoded Tip Tilt Embedded Screens #1326

Open
wants to merge 13 commits into
base: master
Choose a base branch
from

Conversation

aberges-SLAC
Copy link
Contributor

Description

Adds:

  • SmarActEncodedTipTilt.embedded.ui
  • SmarActEncodedTipTilt.embedded.py

New features beyond typical motor motion:

  • Homing sequence for SmarAct STT##.#iv stages (ref A -> ref B -> ref A)
  • Calibration sequence for SmarAct STT##.#iv stages (calib A -> ref A -> calib B -> ref B -> calib A -> ref A)

Couple caveats:
Don't love that I'm reusing classes and code from SmarActTipTilt but the alternatives for importing it as a module are hairy at best.

Motivation and Context

As per issue #1325, we need an embedded screen that successfully bundles the open & closed loop motions for these stages, and I have done just that!

How Has This Been Tested?

I've tested this screen and code out on an STT50.8iv stage in the LRL. See video of the demo below!

If you want to take it for a spin, you can use this script: /cds/home/a/aberges/bin/test_tip_tilt

Where Has This Been Documented?

Documented in the code, release notes, and this PR!

Screenshots (if appropriate):

smaract_encoded_tip_tilt_demo.mp4

Pre-merge checklist

  • Code works interactively
  • Code contains descriptive docstrings, including context and API
  • New/changed functions and methods are covered in the test suite where possible
  • Test suite passes locally
  • Test suite passes on GitHub Actions
  • Ran docs/pre-release-notes.sh and created a pre-release documentation page
  • Pre-release docs include context, functional descriptions, and contributors as appropriate

Copy link
Contributor

@tangkong tangkong left a comment

Choose a reason for hiding this comment

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

All the duplication is annoying, as you point out. I see a couple of ways to approach this, none of which need be implemented in this PR:

  • Do some funky python-y import stuff by calling out the file name specifically
  • Move all of these into some shared widget library, maybe typhos itself or possibly reviving pcdswidgets.
    • You'd then import those widgets from these files, just assuming that pcdswidgets is installed. (we don't list it as a dependency, similar to your use of typhos/pydm here)

All that aside, I think this looks pretty reasonable at a first readthrough. I'll take it for a spin and see how it looks

# Calibrate first
self._status = axis.do_calib.put(1)
# Have to manually sleep :[
sleep(5)
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we get an explanation of why you need to sleep? It'd be nice if we could instead wait on some signal to let us know that calibration is done, because as is this is a brittle, hard-coded 5s sleep

Copy link
Contributor Author

@aberges-SLAC aberges-SLAC Mar 5, 2025

Choose a reason for hiding this comment

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

Because calib causes the motor to move, but it does not necessarily update device.motor_is_moving accurately when it is doing the calibration sequence. I tried a few iterations of this, but barring:

while device.motor_is_moving.get():
    sleep(1)

I don't really have a way around it. From inherited ophyd signals the device.home has a wait incorporated which lets me do this safely during homing but not calib.

I do agree, the hard coded 5s sleep is my least favorite solution

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You know, what I could do (and is probably smarter) is just add some calibrated signal to the SmarAct closed loop device class that reads the :STATE_RBV pv and checks the bit for "is calibrated" like I do manually on the LEDs.

Caveat is that it breaks on older common IOC versions because it was originally an mbbi record and not mbbidirect.

Copy link
Contributor

Choose a reason for hiding this comment

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

Would you time out on a calibration failure?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I guess I could build one into a func for it?

But this method is actually flawed too because if you attempt to re-calibrate a stage you'll get a false-positive.

Also, example of the motor_is_moving signal not doing its job:

Screen.Recording.2025-03-06.112045.mp4

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, but if I hardcode an initial sleep(1) into wait_on_calib to wait for motor_is_moving to change, then it works.

However the first motor_is_moving for an axis gets missed, apparently. So if it is the first time calling wait_on_calib for that axis, it'll just see motor_is_moving == 0 which is really annoying.

Regardless, I wasn't too far off with my hardcoded estimate. Looks like it takes on average 3.8-4 seconds.

def set_open_loop(self, axis: str):
"""
A wrapper to set the open-loop widget channels.
Ironically more lines than just hard coding it.
Copy link
Contributor

Choose a reason for hiding this comment

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

But easier to extend! Maybe :)

float(self.ui.tip_tweak_value.text()))
except Exception:
logger.exception('Positive tweak failed on tip')
return
Copy link
Contributor

Choose a reason for hiding this comment

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

You technically don't need these, if you have no returns by the end of the function, it will return None for you

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, very true. I think I had this in there for what seems like superstitious reasons at this point. I'll clear it

@aberges-SLAC
Copy link
Contributor Author

Oh, lol, my commits from my other PR snuck in. We should delete those from this PR

@aberges-SLAC aberges-SLAC changed the title SmarAct Encoded Tip Tilt Embedded Screes SmarAct Encoded Tip Tilt Embedded Screens Mar 5, 2025
tangkong
tangkong previously approved these changes Mar 6, 2025
Copy link
Contributor

@tangkong tangkong left a comment

Choose a reason for hiding this comment

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

Took another look after taking it for a spin, I'm still on board with this once some of the nits are resolved.

Looking back I think they're not enough to really hold this back, so I'll leave my check before I leave.

If you end up vendoring any of the positioner row logic again, we should look into a way to make those utilities more reusable

sequence_progress_bar: QtWidgets.QProgressBar
tip_expert_button: TyphosRelatedSuiteButton
tilt_expert_button: TyphosRelatedSuiteButton
devices: list
Copy link
Contributor

Choose a reason for hiding this comment

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

a list of ophyd devices? We could probably be more specific here.

On a second look I'm not sure if this ever gets used here. Is this necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, this is probably left in from a previous iteration of the class. I can probably delete it because that context help is meaningless now.


Device Features
---------------
- Add the SmarActEncodedTipTilt.embedded screens
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: should preface with the device(s) that was modified.

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?

No devices were technically modified, just added .ui and .py files. Typhos backend will just prioritized these screens if the screen name == device class (i.e. pcdsdevices.epics_motor.SmarActEncodedTipTilt).

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, it's a really nitty nit. Generally we just put the class(es) related to the PR in front of the description, e.g.
SmarActEncodedTipTilt: Added embedded screens

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OH, no that's a valid nit and I see what you mean. Better readability, will commit

self._status = axis.do_calib.put(1)
# Have to manually sleep :[
sleep(5)
progress += 1
Copy link
Contributor

Choose a reason for hiding this comment

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

The march of progress...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right! Much less elegant than what you were doing in BTMS-gui, but it works and I don't have to have separate threads

if (self.tip_panel.minimumWidth() <= self.tip_panel.original_panel_min_width or
self.tilt_panel.minimumWidth() <= self.tilt_panel.original_panel_min_width):
# No change
self.resize_timer.start()
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't fully understand this resize_timer bit. What's this doing? Just preventing the panel from trying to resize before you're done pushing things around?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is something TyphosPositionerRow figured out with the pop-up panel.

Basically, when you first launch the screen and proc detailed screen with the button, not all the signals are guaranteed to be connected and complete. The window will open with a much smaller size over the scroll area (you can see this at the 0:26-0:27 mark of the video). This whole method is forcing the pop-up screen to respect the inner widget's dimensions and resize itself.

You'll see this same block of code in SmarActTipTilt.embedded.py as well

Copy link
Contributor

Choose a reason for hiding this comment

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

QT is a demon sometimes

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