============
How it Works
============
Typhos has three major building blocks that combine into the final display seen
by the operator:
* **TyphosSuite** : The overall view for a Typhos window. It allows the
operator to view all of the loaded components and tools.
* **TyphosDeviceDisplay** : This is the widget created for a standard
``ophyd.Device``. Signals can be organized based on their ``Kind`` and
description.
* **typhos.tools** : These are widgets that interface with external
applications. While you may have other GUIs for these systems,
``typhos.tools`` are built especially to handle the handshaking between all
the information stored on your device and the tool you are interfacing with.
This saves your operator clicks and ensures consistency in use.
All three of the widgets listed above share a similar API for creation.
Instantiating the object by itself handles loading the container widgets and
placing them in the correct place, but these do not accept ``ophyd.Device``
arguments. The reason for this is to ensure that we can use all of the
``typhos`` screens as templates, and regardless or not of whether you have an
``ophyd.Device`` you can always populate the screens by hand. If you do in fact
have an ``ophyd.Device`` every class has an ``add_device`` method and
alternatively and be constructed using the ``from_device`` classmethod.
.. autoclass:: typhos.utils.TyphosBase
:members:
:noindex:
Interpreting a Device
=====================
Typhos interprets the internal structure of the ``ophyd.Device`` to create the
`PyDM` user interface, so the most intuitive way to configure the created
display is to include components on the device itself. This also has the advantage
of keeping your Python API and display in sync, making the transition from
using screens to using an IPython shell seamless.
For the following applications we'll use the ``motor`` simulation contained
within ``ophyd`` itself. We also need to create a ``QApplication`` before we
create any widgets:
.. ipython:: python
from qtpy.QtWidgets import QApplication
app = QApplication([])
.. ipython:: python
:suppress:
from ophyd.sim import motor
Using Happi
^^^^^^^^^^^
While ``happi`` is not a requirement for using ``typhos``, it is recommended.
For more information, visit the `GitHub `_
repository. The main purpose of the package is to store information on our
Ophyd objects so that we can load them in a variety of contexts. If you do not
use ``happi`` you will need to create your objects and displays in the same
process.
From the command-line, using typhos and happi together is easy. For example,
to load an auto-generated typhos screen for your device named ``"my_device"``
would only require the following:
.. code:: bash
$ typhos my_device
typhos automatically configures the happi client, finds your device, and
creates an appropriate screen for it.
If you are looking to integrate typhos at the Python source code level,
consider the following example which uses ``typhos`` with ``happi``:
.. code:: python
import happi
from typhos.plugins import register_client
# Initialize a new JSON based client
client = happi.Client(path='db.json', initialize=True)
# Register this with typhos
register_client(client)
# Add a device to our new database
device = happi.Device(device_class='ophyd.sim.SynAxis',
prefix='Tst:Mtr', args=[], kwargs='{{name}}',
name='my_motor', beamline='TST')
client.add_device(device)
In practice, it is not necessary to call :func:`.register_client` if you have
configured the ``$HAPPI_CFG`` environment variable such that
``happi.Client.from_config`` yields the desired client.
We can now check that we can load the complete ``SynAxis`` object.
.. code:: python
motor = client.load_device(name='my_motor')
Signals of Devices
^^^^^^^^^^^^^^^^^^
When making a custom screen, you can access signals associated with your device
in several ways, in order of suggested use:
1. By using the typhos built-in "signal" plugin to connect to the signal with
the dotted ophyd name, just as you would use in an IPython session.
In the designer "channel" property, specify: ``sig://device_name.attr``
with as many ``.attrs`` required to reach the signal from the top-level
device as needed.
For example, for a motor named "my_motor", you could use:
``sig://my_motor.user_readback``
2. An alternate signal name is available, that which is seen by the data
acquisition system (e.g., the databroker by way of bluesky). Generally,
characters seen as invalid for a MongoDB are replaced with an underscore
(``_``). To check a signal's name, see the ``.name`` property of that
signal.
For example, for a motor named "my_motor", you could use:
``sig://my_motor_user_readback``
3. By PV name directly. Assuming your signal is available through the
underlying control system (EPICS, for example), you could look and see which
PVs your signal talks to and use those directly. That is,
``my_motor.user_readback.pvname`` would tell you which EPICS PV the user
readback uses. From there, you could set the widget's channel to use EPICS
Channel Access with ``ca://pv_name_here``.
Display Signals
^^^^^^^^^^^^^^^
The first thing we'll talk about is showing a group of signals associated with
our ``motor`` object in a basic form called a
:class:`~typhos.TyphosSignalPanel`. Simply inspecting the device reveals a few
signals for us to display
.. ipython:: python
motor.component_names
It is crucial that we understand the importance of these signals to the
operator. In order to glean this information from the object the ``kind``
attributes are inspected. For more information see the `ophyd documentation
`_. A quick inspection of
the various attributes allows us to see how our signals are organized.
.. ipython:: python
# Most important signal(s)
motor.hints
# Important signals, all hints will be found here as well
motor.read()
# Configuration information
motor.read_configuration()
The :class:`.TyphosSignalPanel` will render these, allowing us to select a
subset of the signals to display based on their kind. Below both the
``QtDesigner`` using ``happi`` and the corresponding ``Python`` code is shown
as well:
.. ipython:: python
from typhos import TyphosSignalPanel
panel = TyphosSignalPanel.from_device(motor)
.. figure:: /_static/kind_panel.gif
:scale: 100%
:align: center
Now, at first glance it may not be obvious, but there is a lot of information
here! We know which of these signals an operator will want to control and which
ones are purely meant to be read back. We also have these signals grouped by
their importance to operation, each with a terse human-readable description of
what the ``Signal`` represents.
Filling Templates
^^^^^^^^^^^^^^^^^
Taking this concept further, instead of filling a single panel
:class:`.TyphosDeviceDisplay` allows a template to be created with a multitude
of widgets and panels. ``Typhos`` will find widgets that accept devices, but do
not have any devices already. Typhos comes with some default templates, and you
can cycle between them by changing the ``display_type``
Once again, both the ``Python`` code and the ``QtDesigner`` use cases are
shown:
.. ipython:: python
from typhos import TyphosDeviceDisplay
display = TyphosDeviceDisplay.from_device(motor)
.. figure:: /_static/device_display.gif
:scale: 100%
:align: center
The TyphosSuite
===============
A complete application can be made by loading the :class:`.TyphosSuite`. Below
is the complete code from start to finish required to create the suite. Look at
the ``TyphosSuited.default_tools`` to control which ``typhos.tools`` are
loaded.
.. code:: python
from ophyd.sim import motor
from qtpy.QtWidgets import QApplication
from typhos.suite import TyphosSuite
from typhos.utils import apply_standard_stylesheets
# Create our application
app = QApplication([])
apply_standard_stylesheets() # Optional
suite = TyphosSuite.from_device(motor)
# Launch
suite.show()
app.exec_()
Using the StyleSheet
====================
Typhos ships with two stylesheets to improve the look and feel of the widgets.
When invoking ``typhos`` from the CLI as normal, you can pass
the ``--dark`` flag to use the dark stylesheet instead of the light mode,
and a ``--stylesheet-add`` argument to use your own stylesheet in addition to Typhos's.
If you want to completely ignore Typhos's normal stylesheet loading and use your own,
you can pass the ``--stylesheet-override`` argument. You can pass these arguments
multiple times to include multiple stylesheets.
Typhos also uses the same stylesheet environment variables as PyDM to load additional
stylesheets. The PyDM environment variables respected here are:
- ``PYDM_STYLESHEET``, a path-like variable that should contain file paths to qss
stylesheets if set.
- ``PYDM_STYLESHEET_INCLUDE_DEFAULT``, which should be set to 1 to include the
default PyDM stylesheet or unset to not include it.
The priority order for stylesheets in the case of conflicts is:
1. The explicit ``styleSheet`` property on the display template
2. The style elements from ``--stylesheet-add``
3. User stylesheets from ``PYDM_STYLESHEET_INCLUDE_DEFAULT``
4. Typhos's stylesheet (either the dark or the light variant)
5. The built-in PyDM stylesheet
Outside of the CLI, the stylesheets can be applied using :func:`typhos.apply_standard_stylesheets`.
This function also handles setting the "Fusion" ``QStyle`` which helps
make the interface have an operating system independent look and feel.
Using the Documentation Widget
==============================
Typhos has a built-in documentation helper, which allows for the in-line
linking and display of a user-provided website.
To inform Typhos how to load documentation specific to your facility, please
customize the following environment variables.
1. ``TYPHOS_HELP_URL`` (str): The help URL format string. The help URL will
be formatted with the ophyd device pertinent to the display, such that you
may access its name, PV prefix, happi metadata (if available), and so on.
For example, if a Confluence server exists at
``https://my-confluence-site.example.com/Controls/`` with document names
that match your devices, ``TYPHOS_HELP_URL`` should be set to
``https://my-confluence-site.example.com/Controls/{device.name}``.
If, perhaps, only top-level devices are guaranteed to have documentation,
consider using: ``device.root.name`` instead in the format string.
2. ``TYPHOS_HELP_HEADERS`` (json): headers to pass to HELP_URL. This should be
in a JSON format, such as ``{"my_key":"my_value"}``.
3. ``TYPHOS_HELP_HEADERS_HOSTS`` (str): comma-delimited hosts that headers may
be sent to, aside from the host configured in ``TYPHOS_HELP_URL``.
4. ``TYPHOS_HELP_TOKEN`` (str): An optional token for the bearer authentication
scheme - e.g., personal access tokens with Confluence. This is a shortcut
to add a header ``"Authorization"`` with the value
``"Bearer ${TYPHOS_HELP_TOKEN}"``.
Using the Jira Bug Reporting Widget
===================================
Typhos has an optional built-in widget to generate Jira user stories/bug
reports.
A prerequisite to this support is, of course, a working Jira installation
and a pre-configured issue collector.
1. ``TYPHOS_JIRA_URL`` (str): The Jira issue collector URL. This will resemble
``https://jira.example.com/rest/collectors/1.0/template/custom/...``.
2. ``TYPHOS_JIRA_HEADERS`` (json): headers to pass to the Jira request, if
needed. This should be in a JSON format, such as ``{"my_key":"my_value"}``.
3. ``TYPHOS_JIRA_TOKEN`` (str): An optional token for the bearer authentication
scheme - e.g., personal access tokens with Confluence. This is a shortcut
to add a header ``"Authorization"`` with the value
``"Bearer ${TYPHOS_JIRA_TOKEN}"``.
4. ``TYPHOS_JIRA_EMAIL_SUFFIX`` (str): the default e-mail suffix to put on
usernames, such as ``"@example.com"``.
Launching the Examples
======================
There are example screens in the ``typhos.examples`` submodule. After
installing ``typhos``, you can launch them as follows:
- ``python -m typhos.examples.panel``
- ``python -m typhos.examples.positioner``