Getting started with GORT

What is GORT?

gort (Genetically Organized Robotic Technology) is a high-level library to interact with the Local Volume Mapper (LVM) observatory.

The LVM software is based on the concept of actor. An actor is a consumer that receives commands from a client, executes an action (expose an spectrograph, open the dome), and communicates back to the user via replies. LVM uses AMQP/RabbitMQ as the message passing system for commands and replies.

gort provides three levels of abstraction for accessing the LVM infrastructure:

  • The lowest level access is a programmatic API to interact with the individual actors that run in the LVM infrastructure.

  • On top of that, gort defines a series of GortDeviceSet and GortDevice classes that expose specific device functionality that is of general use. These classes provide less comprehensive access to the actor devices, but encapsulate the main features that a user is likely to use, and allow to command multiple devices as one, e.g., to send all four telescopes to zenith.

  • The highest level part of gort provides the tools for unassisted observing, e.g., observing a tile. Ultimately, gort provides a robotic mode that takes care of all aspects of observing.

Minimal example

The following is a minimum example of how to use the Gort client to connect to the actor system and retrieve the status of the science telescope.

This code must be run in one of the LVM mountain servers with access to the RabbitMQ exchange.

>>> from gort import Gort

>>> g = await Gort().init()
>>> g.connected()
True

>>> await g.telescopes.sci.update_status()
{'is_tracking': False,
'is_connected': True,
'is_slewing': False,
'is_enabled': False,
'ra_j2000_hours': 9.31475952237581,
'dec_j2000_degs': 26.2017449132552,
'ra_apparent_hours': 9.33749451003047,
'dec_apparent_degs': 26.1043875064315,
'altitude_degs': -61.6417256185769,
'azimuth_degs': 88.1701770599825,
'field_angle_rate_at_target_degs_per_sec': 0.0,
'field_angle_here_degs': -14.3931305251519,
'field_angle_at_target_degs': 0.0,
'axis0': {'dist_to_target_arcsec': 0.0,
'is_enabled': False,
'position_degs': 308.137461864407,
'rms_error_arcsec': 0.0,
'servo_error_arcsec': 0.0},
'axis1': {'dist_to_target_arcsec': 0.0,
'is_enabled': False,
'position_degs': 308.137461864407,
'rms_error_arcsec': 0.0,
'servo_error_arcsec': 0.0,
'position_timestamp': '2023-03-11 16:54:22.5626'},
'model': {'filename': 'DefaultModel.pxp',
'num_points_enabled': 99,
'num_points_total': 111,
'rms_error_arcsec': 18.1248458630523,
'position_degs': 22.8931398305085,
'position_timestamp': '2023-03-11 16:54:22.5626'},
'geometry': 1}

gort is an asynchronous library which enables multiple processes to run at the same time without blocking the event loop. gort is written using asyncio. A certain familiarity with asynchronous programming is recommended, but at a minimum, many of gort’s functions and methods are actually coroutines that need to be awaited when called. Awaiting a coroutine informs the event manager that other coroutines can be run at the same time, allowing concurrency. In the API documentation, coroutines are prefixed by an async label. Those methods and functions need to be awaited.

Programmatic actor API

AMQP actors typically receive commands as a string with CLI-like format. For example, to expose spectrograph sp1 and take a dark of 900 seconds one would do

The programmatic interface allows to convert this command to an asynchronous coroutine like

where remote_actor is a RemoteActor instance that represents the lvmscp.sp1 actor.

Remote actors can be added to a Gort instance by calling the add_actor method with the name of the actor. This requires the actor to be running CLU 2.0+ and accept the get-command-model command

In practice, when an instance of Gort is created, most if not relevant actors are added as remote actors and initialised, and can be accessed from the Gort.actors dictionary

The list of available commands is accessible as a dictionary of RemoteCommand under the commands attribute

These RemoteCommand can be called and awaited with the arguments the command accepts

RemoteCommand returns an ActorReply which includes all the replies generated by the command, which can be accessed as a list under ActorReply.replies. It’s often convenient to flatten all the replies into a single dictionary of keyword-value

Under the hood, RemoteCommand are implemented using unclick, a reverse parser for click. Some features and options may not be fully implemented.

Device sets

Gort defines a series of GortDeviceSet objects that allow the user to communicate with the various infrastructure devices at a relatively high level. Each GortDeviceSet is composed of one or more GortDevice, each associated to a physical device and with an associated actor.

For example, Gort.telescopes provides methods to command all four telescopes. The TelescopeSet is composed of four Telescope devices, sci, skye, skyw, spec that provide access to a single telecope. This allows to, for example, move all telescopes to zenith as one

or command only one telescope

Devices can have their own subdevices. For example all the Telescope instances have Focuser devices that allow to command the focuser

More details on how to use the device sets for observing, with code examples, are provided here.

Observing a target

To observe a science target along with appropriate calibrators we define a Tile object either from the scheduler

of from coordinates, allowing calibrators to be filled out by the scheduler

A Tile can then be observed using GortObserver or, more conviniently, with Gort.observe_tile

To learn more about observing tiles, see the corresponding section.

Using gort in IPython

gort can generally be used in IPython, but note that there’s a small caveat. As described here, IPython does not keep a running event loop while a command is not being executed. This means that Gort cannot keep a connection open to the RabbitMQ exchange and eventually the connection closes.

Gort will try to recreate the connection to the exchange when needed, if it finds it closed, but this can fail in some corner cases. In this case simply recreate the Gort client with

This issue should not affect running gort on an script or in a Jupyter notebook, which runs a persistent background event loop.