Skip to content

Commit

Permalink
Merge branch 'robot_fire' of github.com:yannachen/haven into robot_fire
Browse files Browse the repository at this point in the history
  • Loading branch information
yannachen committed Jun 17, 2024
2 parents e9571b5 + e4497e4 commit 862491e
Show file tree
Hide file tree
Showing 15 changed files with 357 additions and 93 deletions.
145 changes: 116 additions & 29 deletions src/firefly/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@
from PyQt5.QtWidgets import QStyleFactory
from qtpy import QtCore, QtWidgets
from qtpy.QtCore import Signal
from qtpy.QtGui import QKeySequence
from qtpy.QtWidgets import QAction

from haven import load_config
from haven import load_instrument as load_haven_instrument
from haven import registry
from haven.exceptions import ComponentNotFound
from haven.exceptions import ComponentNotFound, InvalidConfiguration
from haven.instrument.device import titelize

from .main_window import FireflyMainWindow, PlanMainWindow
Expand All @@ -43,7 +44,6 @@

class FireflyApplication(PyDMApplication):
default_display = None
xafs_scan_window = None

# For keeping track of ophyd devices used by the Firefly
registry: Registry = None
Expand Down Expand Up @@ -91,10 +91,12 @@ class FireflyApplication(PyDMApplication):
# Signals responding to queueserver changes
queue_length_changed = Signal(int)
queue_status_changed = Signal(dict)
queue_in_use_changed = Signal(bool) # length > 0, or running
queue_environment_opened = Signal(bool) # Opened or closed
queue_environment_state_changed = Signal(str) # New state
queue_manager_state_changed = Signal(str) # New state
queue_re_state_changed = Signal(str) # New state
queue_empty_changed = Signal(bool) # Whether queue is empty
# queue_devices_changed = Signal(dict) # New list of devices

# Actions for controlling the queueserver
Expand All @@ -107,6 +109,7 @@ class FireflyApplication(PyDMApplication):
halt_runengine_action: QAction
start_queue: QAction
queue_autostart_action: QAction
queue_stop_action: QAction
queue_open_environment_action: QAction
check_queue_status_action: QAction

Expand All @@ -130,11 +133,17 @@ def __del__(self):
self._queue_thread.wait(msecs=5000)
assert not self._queue_thread.isRunning()

def _setup_window_action(self, action_name: str, text: str, slot: QtCore.Slot):
def _setup_window_action(
self, action_name: str, text: str, slot: QtCore.Slot, shortcut=None, icon=None
):
action = QtWidgets.QAction(self)
action.setObjectName(action_name)
action.setText(text)
action.triggered.connect(slot)
if shortcut is not None:
action.setShortcut(QKeySequence(shortcut))
if icon is not None:
action.setIcon(qta.icon(icon))
setattr(self, action_name, action)
return action

Expand Down Expand Up @@ -268,15 +277,15 @@ def setup_window_actions(self):
)
# Actions for executing plans
plans = [
# (plan_name, text, display file)
("count", "&Count", "count.py"),
("move_motor", "&Move motor", "move_motor_window.py"),
("line_scan", "&Line scan", "line_scan.py"),
("grid_scan", "&Grid scan", "grid_scan.py"),
("xafs_scan", "&XAFS Scan", "xafs_scan.py"),
# (plan_name, text, display file, shortcut, icon)
("count", "&Count", "count.py", "Ctrl+Shift+C", "mdi.counter"),
("move_motor", "&Move motor", "move_motor_window.py", None, None),
("line_scan", "&Line scan", "line_scan.py", "Ctrl+Shift+L", None),
("grid_scan", "&Grid scan", "grid_scan.py", None, None),
("xafs_scan", "&XAFS Scan", "xafs_scan.py", "Ctrl+Shift+X", None),
]
self.plan_actions = []
for plan_name, text, display_file in plans:
for plan_name, text, display_file, shortcut, icon in plans:
slot = partial(
self.show_plan_window, name=plan_name, display_file=display_file
)
Expand All @@ -286,6 +295,10 @@ def setup_window_actions(self):
action.setObjectName(action_name)
action.setText(text)
action.triggered.connect(slot)
if shortcut is not None:
action.setShortcut(QKeySequence(shortcut))
if icon is not None:
action.setIcon(qta.icon(icon))
self.plan_actions.append(action)
# Action for showing the run browser window
self._setup_window_action(
Expand All @@ -298,6 +311,7 @@ def setup_window_actions(self):
action_name="launch_queuemonitor_action",
text="Queue Monitor",
slot=self.launch_queuemonitor,
shortcut="Ctrl+Q",
)
# Action for showing the beamline scheduling window
self._setup_window_action(
Expand All @@ -316,6 +330,7 @@ def setup_window_actions(self):
action_name="show_voltmeters_window_action",
text="&Voltmeters",
slot=self.show_voltmeters_window,
icon="ph.faders-horizontal",
)
# Launch log window
self._setup_window_action(
Expand All @@ -328,6 +343,7 @@ def setup_window_actions(self):
action_name="show_energy_window_action",
text="Energy",
slot=self.show_energy_window,
shortcut="Ctrl+E",
)
self.show_energy_window_action.setIcon(qta.icon("mdi.sine-wave"))
# Launch camera overview
Expand Down Expand Up @@ -356,40 +372,96 @@ def setup_runengine_actions(self):
self.check_queue_status_action = QtWidgets.QAction(self)
# Navbar actions for controlling the run engine
actions = [
("pause_runengine_action", "Pause", "fa5s.stopwatch"),
("pause_runengine_now_action", "Pause Now", "fa5s.pause"),
("resume_runengine_action", "Resume", "fa5s.play"),
("stop_runengine_action", "Stop", "fa5s.stop"),
("abort_runengine_action", "Abort", "fa5s.eject"),
("halt_runengine_action", "Halt", "fa5s.ban"),
("start_queue_action", "Start", "fa5s.play"),
# (action_name, text, icon, shortcut, tooltip)
(
"pause_runengine_action",
"Pause",
"fa5s.stopwatch",
"Ctrl+D",
"Pause the current plan at the next checkpoint.",
),
(
"pause_runengine_now_action",
"Pause Now",
"fa5s.pause",
"Ctrl+C",
"Pause the run engine now.",
),
(
"resume_runengine_action",
"Resume",
"fa5s.play",
None,
"Resume the previously-paused run engine at the last checkpoint.",
),
(
"stop_runengine_action",
"Stop",
"fa5s.stop",
None,
"End the current plan, marking as success.",
),
(
"abort_runengine_action",
"Abort",
"fa5s.eject",
None,
"End the current plan, marking as failure.",
),
(
"halt_runengine_action",
"Halt",
"fa5s.ban",
None,
"End the current plan immediately, do not clean up.",
),
("start_queue_action", "Start", "fa5s.play", None, "Start the queue."),
]
self.queue_action_group = QtWidgets.QActionGroup(self)
for name, text, icon_name in actions:
for name, text, icon_name, shortcut, tooltip in actions:
action = QtWidgets.QAction(self.queue_action_group)
icon = qta.icon(icon_name)
action.setText(text)
action.setCheckable(True)
action.setCheckable(False)
action.setIcon(icon)
action.setToolTip(tooltip)
if shortcut is not None:
action.setShortcut(QKeySequence(shortcut))
setattr(self, name, action)
# Actions that control how the queue operates
actions = [
# Attr, object name, text
("queue_autostart_action", "queue_autostart_action", "&Autoplay"),
# Attr, object name, text, tooltip
(
"queue_autostart_action",
"queue_autostart_action",
"&Autoplay",
"If enabled, the queue will start when items are added.",
),
(
"queue_stop_action",
"queue_stop_action",
"Stop Queue",
"Instruct the queue to stop after the current item is done.",
),
(
"queue_open_environment_action",
"queue_open_environment_action",
"&Open Environment",
"If open (checked), the queue server is able to run plans.",
),
]
for attr, obj_name, text in actions:
for attr, obj_name, text, tooltip in actions:
action = QAction()
action.setObjectName(obj_name)
action.setText(text)
action.setToolTip(tooltip)
setattr(self, attr, action)
# Customize some specific actions
self.queue_autostart_action.setCheckable(True)
self.queue_autostart_action.setChecked(True)
self.queue_stop_action.setCheckable(True)
self.queue_stop_action.setToolTip(
"Tell the queueserver to stop after this current plan is done."
)
self.queue_open_environment_action.setCheckable(True)

def _prepare_device_windows(
Expand Down Expand Up @@ -479,7 +551,7 @@ def _prepare_device_windows(
action.triggered.connect(slot)
window_slots.append(slot)

def prepare_queue_client(self, api=None):
def prepare_queue_client(self, client=None, api=None):
"""Set up the QueueClient object that talks to the queue server.
Parameters
Expand All @@ -488,10 +560,19 @@ def prepare_queue_client(self, api=None):
queueserver API. Used for testing.
"""
# Load the API for controlling the queueserver.
if api is None:
api = queueserver_api()
try:
api = queueserver_api()
except InvalidConfiguration:
log.error(
"Could not load queueserver API "
"configuration from iconfig.toml file."
)
return
# Create the client object
client = QueueClient(api=api)
if client is None:
client = QueueClient(api=api)
self.queue_open_environment_action.triggered.connect(client.open_environment)
self._queue_client = client
# Connect actions to slots for controlling the queueserver
Expand All @@ -502,6 +583,11 @@ def prepare_queue_client(self, api=None):
partial(client.request_pause, defer=False)
)
self.start_queue_action.triggered.connect(client.start_queue)
self.resume_runengine_action.triggered.connect(client.resume_runengine)
self.stop_runengine_action.triggered.connect(client.stop_runengine)
self.halt_runengine_action.triggered.connect(client.halt_runengine)
self.abort_runengine_action.triggered.connect(client.abort_runengine)
self.queue_stop_action.triggered.connect(client.stop_queue)
self.check_queue_status_action.triggered.connect(
partial(client.check_queue_status, True)
)
Expand All @@ -510,11 +596,14 @@ def prepare_queue_client(self, api=None):
# Connect signals/slots for queueserver state changes
client.status_changed.connect(self.queue_status_changed)
client.length_changed.connect(self.queue_length_changed)
client.in_use_changed.connect(self.queue_in_use_changed)
client.autostart_changed.connect(self.queue_autostart_action.setChecked)
client.environment_opened.connect(self.queue_environment_opened)
self.queue_environment_opened.connect(self.set_open_environment_action_state)
client.environment_state_changed.connect(self.queue_environment_state_changed)
client.manager_state_changed.connect(self.queue_manager_state_changed)
client.re_state_changed.connect(self.queue_re_state_changed)
client.queue_stop_changed.connect(self.queue_stop_action.setChecked)
client.devices_changed.connect(self.update_devices_allowed)
self.queue_autostart_action.toggled.connect(
self.check_queue_status_action.trigger
Expand Down Expand Up @@ -660,9 +749,7 @@ def show_device_window(

def show_status_window(self, stylesheet_path=None):
"""Instantiate a new main window for this application."""
self.show_window(
FireflyMainWindow, ui_dir / "status.py", name="beamline_status"
)
self.show_window(PlanMainWindow, ui_dir / "status.py", name="beamline_status")

@QtCore.Slot()
def show_run_browser_window(self):
Expand Down
2 changes: 1 addition & 1 deletion src/firefly/launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ def main(default_fullscreen=False, default_display="status"):
asyncio.set_event_loop(event_loop)

# Define devices on the beamline (slow!)
app.setup_instrument()
app.setup_instrument(load_instrument=not pydm_args.no_instrument)

# Get rid of the splash screen now that we're ready to go

Expand Down
27 changes: 17 additions & 10 deletions src/firefly/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import qtawesome as qta
from pydm import data_plugins
from pydm.main_window import PyDMMainWindow
from qtpy import QtCore, QtGui, QtWidgets
from qtpy import QtGui, QtWidgets

from haven import load_config

Expand Down Expand Up @@ -93,6 +93,12 @@ def customize_ui(self):
_label = QtWidgets.QLabel()
_label.setText("Queue:")
bar.addPermanentWidget(_label)
self.ui.queue_length_label = QtWidgets.QLabel()
self.ui.queue_length_label.setToolTip(
"The length of the queue, not including the running plan."
)
self.ui.queue_length_label.setText("(??)")
bar.addPermanentWidget(self.ui.queue_length_label)
self.ui.environment_label = QtWidgets.QLabel()
self.ui.environment_label.setToolTip(
"The current state of the queue server environment."
Expand All @@ -108,6 +114,7 @@ def customize_ui(self):
bar.addPermanentWidget(self.ui.re_label)
# Connect signals to the status bar
app.queue_environment_state_changed.connect(self.ui.environment_label.setText)
app.queue_length_changed.connect(self.update_queue_length)
app.queue_re_state_changed.connect(self.ui.re_label.setText)
# Log viewer window
if hasattr(app, "show_logs_window_action"):
Expand All @@ -124,6 +131,7 @@ def customize_ui(self):
self.ui.menubar.addAction(self.ui.queue_menu.menuAction())
for action in app.queue_action_group.actions():
self.ui.queue_menu.addAction(action)
self.ui.queue_menu.addAction(app.queue_stop_action)
self.ui.queue_menu.addSeparator()
# Queue settings for the queue client
self.ui.queue_menu.addAction(app.launch_queuemonitor_action)
Expand Down Expand Up @@ -221,6 +229,9 @@ def customize_ui(self):
self.ui.menuView.addAction(app.show_status_window_action)
self.ui.menuSetup.addAction(app.show_bss_window_action)
self.ui.menuSetup.addAction(app.show_iocs_window_action)
# Make tooltips show up for menu actions
for menu in [self.ui.menuSetup, self.ui.detectors_menu, self.ui.queue_menu]:
menu.setToolTipsVisible(True)

def show_status(self, message, timeout=0):
"""Show a message in the status bar."""
Expand All @@ -240,6 +251,9 @@ def update_window_title(self):
title += " [Read Only Mode]"
self.setWindowTitle(title)

def update_queue_length(self, new_length: int):
self.ui.queue_length_label.setText(f"({new_length})")


class PlanMainWindow(FireflyMainWindow):
"""A Qt window that has extra controls for a bluesky runengine."""
Expand All @@ -263,7 +277,7 @@ def setup_navbar(self):
navbar.addAction(app.resume_runengine_action)
navbar.addAction(app.stop_runengine_action)
navbar.addAction(app.abort_runengine_action)
navbar.addAction(app.halt_runengine_action)
# navbar.addAction(app.halt_runengine_action)

def customize_ui(self):
super().customize_ui()
Expand All @@ -272,14 +286,7 @@ def customize_ui(self):
from .application import FireflyApplication

app = FireflyApplication.instance()
app.queue_length_changed.connect(self.set_navbar_visibility)

@QtCore.Slot(int)
def set_navbar_visibility(self, queue_length: int):
"""Determine whether to make the navbar be visible."""
log.debug(f"Setting navbar visibility. Queue length: {queue_length}")
navbar = self.ui.navbar
navbar.setVisible(queue_length > 0)
app.queue_in_use_changed.connect(self.ui.navbar.setVisible)


# -----------------------------------------------------------------------------
Expand Down
Loading

0 comments on commit 862491e

Please sign in to comment.