LCLSHome

LCLSHome is a unique page with a unique structure.  Fundamentally, it is four displays combined into one.  There are two table views, and two embedded display views.  One of each for each accelerator.  To facilitate the transfer of data between these two sets of displays, Python dictionaries are used heavily.  This allows the code to only access a minimum amount of variables. 

The two main views are referred to as Table View and Display View.  Table View is a large grid, containing buttons that link to the Display View.  Many buttons are color coded, to represent the alarm state of that particular Subsystem / Area combination.  The alarm PV associated with each button is collected automatically via the directory service.  This means that adding new alarms is transparent to the LCLSHome maintainer or system engineer. 

LCLSHome Table View

Dictionaries

There are two main dictionaries for LCLSHome, self.nc and self.sc.  These are instantiated in the main body of the LCLSHome class, but have additional keys added throughout the code.  The keys for both dictionaries are identical but the data differs.  The keys are:

  • Areas:  These are the common area names for each column of the Table view.  Areas downstream of the linac are duplicated for each dictionary, e.g. UNDH, DMPS, BSY, etc.  
  • Subsystems: Full names for each subsystems that are the rows of the Table view. 
  • Abbreviations: Abbreviated subsystem name to match the naming scheme of the $PYDM repository, e.g., 'Magnet' → MGNT, 'BPM/Toro/BLen' → GADC, etc.
  • X Index: Used to index the columns of the EDM Area Main page
  • Y Index: Used to index the rows of the EDM Area Main page

These two main dictionaries are combined into a higher level dictionary called self.linac_info, with the keys "NC" and "SC".  This main variable is passed to many functions in the script and is frequently renamed to "info". 

Interface

Frames

All frames in this interface are instantiated through a function called self.make_frame()make_frame() takes either 'H' or 'V' as input, to indicate whether a Horizontal or Vertical layout is desired.  The return frame has either a QVBoxLayout or a QHBoxLayout, which can be accessed with frame.layout(). When adding multiple widgets to a layout, use self.fillLayout()fillLayout() takes two arguments, a layout and a list of widgets, and adds the widgets to the layout in the order of the list.

The main layout of LCLSHome is a QVBoxLayout.  The main layout is ordered as such:

  1. Title Frame: Holds the title of the page, as well as the help button and LCLS Logo.
  2. Navigation Frame: Holds controls to navigate between linacs and set the available destinations.  Also holds the button that links to the EDM Area Main page.
  3. Content Frame: The main frame which holds several nested frames.  This is to facilitate switching between Table and Display views.  There are separate 'Table Frame', 'Content Frame' and 'Inner Frame' objects inside 'Content Frame'.  These objects keys are held inside of the main dictionary for each Linac.  The Applications frame also exists in the Table Frame.
  4. Footer Frame: Displays the current network and time of day.

Tables

Each main table is keyed to self.linac_info[<linac>]["Table"].  The tables are first instantiated through self.make_table() and then added to the "Table Frame" in the function self.create_tables()

The table is populated with buttons via the function self.createTableButtons().  This function begins by making alarm PV strings.  By iterating through each table cell, it collects the row and column and combines the abbreviations into a string and appends the PV suffix.  For example, RF / IN20 becomes RF:IN20:1:STATSUMY.  This created alarm PV is compared against the list of alarm PVs generated from the Directory Service.  Depending on the result of this comparison, one of two custom widget types is inserted to the table.  If the generated PV name is in the Directory Service list, then a table_button widget is inserted.  The table_button widget is the alarm-reporting button.  If there is no match, then a blank_button is inserted.  The widget blank_button is a button with no alarm state.

Embedded Displays

Similarly to the table instantiation, the embedded displays are made with the function self.createDisplays().  The embedded displays are housed in a sub-frame of the Content Frame, called "Inner Frame".  This is also a key in the main self.linac_info dictionary.  The embedded displays themselves live at self.linac_info["Display"].

Additionally, the Display view of LCLSHome has two sets of buttons; one for the Subsystems and one for the Areas.  These buttons are generated by either self.createAreaButtons() or self.createSubButtons() and each return a list of widgets.  All buttons (in both lists) are connected to the function self.set_filename(), which is the main driver when navigating between different embedded displays.

Applications

On the right-hand side of the Table View are the Applications.  These buttons link to different commonly used applications.  This list is generated programmatically from the lclshome_apps.py file.  lclshome_apps.py contains one main Python dictionary called apps_dict.  This dictionary works in conjunction with the function self.createApplications() in lclshome.py. 

The dictionary is organized so that each element is a list.  The 0th position of the list holds the type of widget to be inserted, and the following positions contain the relevant information.  This could be either a shell command, a PyDM display filename, or an EDM display filename.  There is a custom widget type, AlarmButton, used in this dictionary.  AlarmButton is a PyDMRelatedDisplayButton with an alarm border.  A PV is assigned to it and will indicate the alarm state around the button.

Functions

change_view()

This function manages which content and destinations frame are shown.  It hides all frames, resizes the window, and then shows the active frames.  It determines the current source via self.nc['Radio Button'], then checks if a file has been set for the current source's display. If a file has been set, it shows the display, otherwise it shows the source's table.  change_view is connected to the "Source" radio buttons and is called by set_filename, clearEmbeddedDisplay, and LCLSHome.__init__.

set_filename()

This function sets the embedded display file for a source.  It is called every time a button from the Table View is clicked, or whenever an Area or Subsystem button in Display View is clicked.  It takes in the selected Source as an argument, and optionally the Subsystem Abbreviation and/or the Area.  For arguments that aren't provided, the value is determined by finding which button is checked in the lists of subsystem and area buttons in the display frame.

The function next generates the filename to be display in the Embedded Display.  Using the abbreviation and area, a filename of the format <abbreviation>/<area>_main.ui is saved as a string, e.g., ntwk/li24_main.ui.  A long macro string is set next to keep continuity with the previous EDM equivalent panel.  This macro string is generated using the abbreviations and area input.  After the filename and macros are set, change_view is called to show the selected display.

clearEmbeddedDisplay()

This function is only used by the 'Home View' button in the Display View.  It clears the filename and macros from the current source's display, then calls change_view to show the current source's table.

handle_arguments()

The argument parser defined at the beginning of the script is managed by this function.  This function is ran immediately after the main user interface is instantiated.  It is also used by self.handle_button() to launch LCLSHome in the appropriate state in a new window.  The arguments gathered by the argument parser are passed to this function.  self.handle_arguments() then uses these to set the UI state.

toggle_destinations()

When choosing available destinations, this function looks at the (up to) 16 available states and determines the appropriate table state.  For the SC linac, a list of the four available destinations states is used as a binary number, e.g., [0,1,1,1].  This binary is converted to an integer, and a series of if statements determines what columns need to be shown and hidden in the table.  The same approach is taken for the NC linac but there are only four available states. 

handle_button()

Each table button in the Table View have additional functionality tied to the mouse right-click.  When the table buttons are instantiated in self.createTableButtons(), they are all connected to this one function.  Depending on the QApplication.keyboardModifiers(), the user can either open the Display View in a new window, or open the EDM version of that particular panel. 

LCLSHome Dev Script

A development version of lclshome.py  exists that can be used to display in-development pages in the LCLSHome environment.  The script supports pointing any number of single rows (e.g., Network, Cryo, Vacuum, etc.) at unique repositories on the controls network (the default path is $TOOLS/pydm/display/).  This script is not version controlled, and has been out-of-sync with lclshome.py since Jan 2024.

To use this script, copy /u/cd/devagr/lclshome_dev.py  and /u/cd/devagr/lclshome_apps.py  to your working directory.  At the top of lclshome_dev.py , change LOCAL_SRC  to the directory containing your development subsystem repos, and set TEST_SUBSYSTEMS  to a list of the subsystems that you would like to test.  You can search for these variables in the code to see how the replacement works.  If your working repos aren't in the same directory as each other, you can use additional if  blocks to point to custom paths.

LCLSHome Dev Mode

The dedicated dev script above is being replaced with a dev mode within LCLSHome itself.  While it hasn't been released, you can use a beta version if you don't mind loading dev versions of all subsystems and all your working repos are in the same directory.

If your working repos are located under ~/tools/pydm/display/ (mirroring $PYDM)

#get the beta script
cd ~/tools/pydm/display/lcls/lclshome/
git fetch
git checkout -t origin/devagr/dev-mode-beta

#get the local launch script
cd ~
cp /u/cd/devagr/lclslocal .

#run LCLSHome using any available development versions 
./lclslocal --DEV

If your working repos are not under ~/tools/pydm/display/ 

#get the beta script
cd {path_to_your_repos}/lcls/lclshome/
git fetch
git checkout -t origin/devagr/dev-mode-beta

#edit the paths in getFilepath to point to {path_to_your_repos}
vim lclshome.py

#run LCLSHome using any available development versions 
pydm --hide-nav-bar --hide-status-bar --log_level ERROR --stylesheet $TOOLS/pydm/stylesheet/default.qss {path_to_your_repos}/lcls/lclshome/lclshome.py  --DEV &
  • No labels