.. _quickstart: Quickstart Guide ###################### This is intended as a quick guide for people already familiar with GPI and the original ``gpilib`` to get up and running with ``gpilib2``. For all others, it is strongly recommended that you read the rest of this documentation before running the instrument. .. note:: All code blocks in this page are intended to be executed inside a python interactive session (and preferably an ``ipython`` session). Some code blocks also include sample outputs from running certain commands. gpilib2 Objects ======================= In normal operation, the main entry point to instrument control is via the instantiation of a :py:class:`~gpilib2.gpi` object: .. code-block:: python import gpilib2.gpi gpi = gpilib2.gpi.gpi() The initialization of a :py:class:`~gpilib2.gpi` object automatically generates multiple other objects (as shown in :numref:`fig:gpilib2_classes`), which are all set as its attributes. Each of these objects groups together similar mechanisms or similar functionality. Frequently this corresponds to a single GPI :term:`assembly` (for example, the :py:class:`~gpilib2.powerbars` object exactly maps to the powerbar assembly). In other cases, commands from multiple :term:`assemblies` are bundled together (for example, the :py:class:`~gpilib2.coronagraph` object bundles the :term:`apodizer`, :term:`FPM`, and :term:`Lyot`, and thus calls commands from the :term:`PPM`, :term:`FPM`, and :term:`IFS` :term:`assemblies`). .. _fig:gpilib2_classes: .. figure:: gpilib2_classes.png :width: 100.0% :alt: gpilib2 classes ``gpilib2`` classes. All sub-blocks are object attributes of a parent object and all individual blocks are attributes of the top-level ``gpi`` object. Any and all of the objects generated by creating an object instance of :py:class:`~gpilib2.gpi` can also be instantiated on their own (i.e., you can always create a bare instance of :py:class:`~gpilib2.coronagraph`), but there is typically no reason to do so, other than for code debugging tasks. The rpc Object ---------------- The first object instantiated when initializing :py:class:`~gpilib2.gpi` is :py:class:`~gpilib2.rpc`. This is then passed to the ``__init__`` methods of all other objects. :py:class:`~gpilib2.rpc` is the main means of communicating with the instrument and includes utility methods for reading and writing :term:`GMB` fields and sending commands via :term:`RPC` calls. The :py:class:`~gpilib2.rpc` is essentially a thin wrapper for a large number of binaries, whose location on disk is stored in the internal dictionary :py:attr:`~gpilib2.rpc.rpc.binaries`. It is possible to create multiple different :py:class:`~gpilib2.rpc` objects within a single session (and you could actually use a separate object for each separate sub-class generated by building a :py:class:`~gpilib2.gpi` object, but, again, in normal usage there is seldom reason to do so. The :py:class:`~gpilib2.rpc` object determines whether you are operating the actual instrument or running in a simulated mode (set by the boolean attribute :py:attr:`~gpilib2.rpc.rpc.sim` and as an input to the ``__init__`` of both :py:class:`~gpilib2.rpc` and :py:class:`~gpilib2.gpi`. .. note:: The ``gpilib2`` is distinct from the built-in :term:`SIM` capabilities of GPI itself. When :py:class:`~gpilib2.rpc` are created wit ``sim=True``, no commands are actually sent to the instrument at all, and all instrument responses are simulated. In order to create a simulated interface, run: .. code-block:: python import gpilib2.gpi gpi = gpilib2.gpi.gpi(sim=True) The :py:class:`~gpilib2.rpc` object also has a verbose mode, toggled by :py:attr:`~gpilib2.rpc.rpc.verb` and as an input to the ``__init__`` of both :py:class:`~gpilib2.rpc` and :py:class:`~gpilib2.gpi`. Verbosity can be toggled on and off at any time by running: .. code-block:: python gpi.rpc.verb = True and .. code-block:: python gpi.rpc.verb = False .. note:: Creating the :py:class:`~gpilib2.rpc` with ``sim=True`` will automatically set ``verb=True`` as well. This can then be turned off, by using the command above, if so desired. Getting Information ======================= All ``gpi`` sub-objects have helpful ``__str__`` methods, such that calling ``print`` on them will display useful information. For example: .. code-block:: python print(gpi.powerbars) will show the current powerbar status, like so: :: ------------------------------------------------------------------------------------------------ | 1| 2| 3| 4| ------------------------------------------------------------------------------------------------ |1| IFS_COMPUTER: ON| IFS_CCRS: ON| TLC_COMPUTER: ON| OMSS_PZT: ON| |2| IFS_SERIAL_PUPIL_CAM: ON| IFS_GALIL_2:OFF| SPARE_PB3_OUTLET2:OFF| GIS: ON| |3| AVCS_ELECTRONICS:OFF| CAL_SHUTTER: ON| AOC_PCI_EXTENDER: ON| SPARE_PB4_OUTLET3:OFF| |4| CAL_OMSS_SERIAL_2: ON| SUPER_CONTINUUM: ON| OMSS_SERIAL_1: ON| ACCELEROMETER_PC:OFF| |5| 1WIRE_M1_M2_M3: ON| OMSS_GALIL_3_4: ON| 1WIRE_M4: ON| AO_TT:OFF| |6| CAL_COMPUTER: ON| CAL_PZT_GALIL_1: ON| SPARE_PB3_OUTLET6: ON| AO_WOOFER:OFF| |7| IFS_DETECTOR_BRICK: ON| IFS_TEMP_VACUUM: ON| SPARE_PB3_OUTLET7:OFF| AOWFS: ON| |8| LIGHT_SOURCE_CHASSIS: ON| CAL_LOWFS_HOWFS: ON| AO_COMPUTER: ON| MEMS:OFF| ------------------------------------------------------------------------------------------------ and: .. code-block:: python print(gpi.coronagraph) will show the current coronagraph setup as: :: apodizer Mask: CLEARGP Motor: [36.003464 -0.137264] fpm Mask: SCIENCE Motor: [154.597107] lyot Mask: Blank Motor: [0.] Querying the GMB ------------------------ All :term:`GMB` variables can be queried with :py:class:`~gpilib2.rpc` method :py:meth:`~gpilib2.rpc.rpc.read_gmb_values`. This method can query a single field, or multiple fields at once: .. code-block:: python gpi.rpc.read_gmb_values('tlc.fpmAss.maskStr') gpi.rpc.read_gmb_values(['tlc.fpmAss.maskStr', 'tlc.ppmAss.maskStr']) .. warning:: Regardless of the input, :py:meth:`~gpilib2.rpc.rpc.read_gmb_values` output will always be a :py:class:`numpy.ndarray`. To see what :term:`GMB` fields are available, use method :py:meth:`~gpilib2.rpc.rpc.list_gmb_fields`. For example, to list all variables set by the :term:`CAL`: .. code-block:: python gpi.rpc.list_gmb_fields('cal') Querying Sensors --------------------- The :py:class:`~gpilib2.sensors` object is used for querying all GPI sensors (both :term:`oneWire` and others). Running a ``print(gpi.sensors)`` will display the current values of **all** named sensors in GPI. This object also has a :py:meth:`~gpilib2.sensors.sensors.get_values` method that allows for querying of specific sensors or groups of sensors. For example: .. code-block:: python gpi.sensors.get_values('Max. MEMS relative humidity') will return: :: {'Max. MEMS relative humidity': 7.865232} while: .. code-block:: python gpi.sensors.get_values('humidity') will return: :: {'Max. EE relative humidity': 46.535496, 'Max. MEMS relative humidity': 7.867756, 'Max. OMSS relative humidity': 30.105785, 'Max. ambient relative humidity': 47.620869, '3rd EE Heat Exchanger Humidity': 66.844627, 'AO EE Heat Exchanger Humidity': 46.535496, 'CAL IFS EE Heat Exchanger Humidity': 36.544643} A complete list of sensor names is available in the attribute :py:attr:`~gpilib2.sensors.sensors.sensornames`. Querying Sensor Logs ------------------------ ``gpilib2`` provides utilities for querying :term:`oneWire` logs via the :py:meth:`~gpilib2.sensors.sensors.query_onewire_logs` method. The logs use sensor ids, and a lookup table is provided by attribute :py:attr:`~gpilib2.sensors.sensors.onewire_keys`. .. note:: The :py:attr:`~gpilib2.sensors.sensors.onewire_keys` attribute only becomes available after running :py:meth:`~gpilib2.sensors.sensors.get_onewire_logs`, which happens automatically the first time :py:meth:`~gpilib2.sensors.sensors.query_onewire_logs` is called. You do not need to know the exact name or id of the sensor you want, since :py:meth:`~gpilib2.sensors.sensors.query_onewire_logs` supports partial name matching and regular expressions. For example, input ``ccr`` will match sensors ``CCR Body temperature, IFS CCR Glycol Output Temp, IFS CCR Glycol Input Temp`` while input ``ccr.*body`` will match only ``CCR Body temperature``. For an overview of Python regex, see: https://docs.python.org/3/library/re.html As an example, we can plot the CCR body and Glycol i/o temperatures over the last 3 days: .. code-block:: python %matplotlib import matplotlib.pyplot as plt import gpilib2.gpi gpi = gpilib2.gpi.gpi() res = gpi.sensors.query_onewire_logs('glycol', last_hours=72) plt.figure(figsize=(10,5)) for key in res: plt.plot(res[key][:,0], res[key][:,1],label=key) plt.legend(bbox_to_anchor=[1.,1.]) plt.subplots_adjust(left=0.05,right=0.55) which produces something like: .. _fig:sample_onewire_plot: .. figure:: sample_onewire_plot.png :width: 100.0% :alt: Sample oneWire log plot oneWire logs for all keys matching query 'glycol' over a period of 3 days. Sending Commands =================== Inasmuch as possible, ``gpilib2`` command names are standardized. All individual components (where relevant) will have ``init``, ``datum``, and ``sim`` methods. All collections of components (i.e., :py:class:`~gpilib2.coronagraph`, etc.) will have ``init_all`` and ``datum_all`` methods. All components and collections of components will have ``move`` methods. The particular syntax of each ``move`` command will vary in the allowed keywords, and can be looked up in this documentation, or by printing the docstring of a particular command from within an interactive ``ipython`` session, for example: .. code-block:: ipython gpi.coronagraph.move? Queueing Commands -------------------- All commands sent through the :py:class:`~gpilib2.rpc` object can be executed immediately (via the :py:meth:`~gpilib2.rpc.rpc.execute` method) or queued (by passing ``queue=True`` to the same method) for later execution (via the :py:meth:`~gpilib2.rpc.rpc.execute_queue` method). Every ``move`` command for all objects accepts a boolean ``queue`` input keyword argument. Execution of a queue involves issuing all pending commands in the queue asynchronously (such that they are executed as quickly as possible). Both single command execution and queue execution will check on the :term:`ack` status of the command, and raise RunTimeError exceptions if any errors are encountered in issuing any of the commands. By default, both single command execution and queue execution will not return until the :term:`GMB` status variable for the command (or all commands in the queue) shows completion or error. In the case of errors, a RunTimeError exception will be raised. If keyword input `nowait` is set, then both :py:meth:`~gpilib2.rpc.rpc.execute` and :py:meth:`~gpilib2.rpc.rpc.execute_queue` will return immediately after checking for a successful :term:`ack`. .. warning:: The ``nowait`` and ``queue`` keywords are primarily intended for use in higher-level functionality where specific sequences of parallel execution have been well mapped out. They are **not** recommended for ordinary interactive use of low-level ``move`` methods, unless you really, *really* know what you're doing. In addition to their ``move`` commands, many of the ``gpi`` sub-objects have utility wrappers allowing for easier commanding of standardized move settings. For example, all components of :py:class:`~gpilib2.source` have an ``extract()`` and ``deploy()`` method to extract and deploy the :term:`ASU` and :term:`CAL Sphere`, and turn their attenuation (and, in the case of the :term:`ASU`, the :term:`SC` power) down and up, respectively.