The Macro Server

The Macro Server object is the sardana server object which manages all high level sardana objects related to macro execution, namely doors, macro libraries and macros themselves.

The main purpose of the Macro Server is to run macros. Macros are just pieces of Python code (functions or classes) which reside in a macro library (Python file). Macros can be written by anyone with knowledge of Python.

The Macro Server is exposed on the sardana server as a Tango device. Through configuration, the Macro Server can be told to connect to a Pool device. This is the most common configuration. You can, however, tell the Macro Server to connect to more than one Pool device or to no Pool devices at all.

When connected to a Pool device(s), the Macro Server uses the Pool device introspection API to discover which elements are available. The existing macros will be able to access these elements (through parameters passed to the macro or using the macro API) and act on them.

In order to be able to run macros, you must first connect to the Macro Server entry point object called Door. A single Macro Server can have many active Doors at the same time but a Door can only run one macro at a time. Each Door is exposed on the sardana server as a Tango device.

You are not in any way restricted to the standard macros provided by the sardana system. You can write as many macros as you need. Writing your own macros is easy. The macro equivalent of Python's Hello, World! example:

from sardana.macroserver.macro import macro

@macro()
def hello_world(self):
    self.output("Hello, World!")

Information on how to write your own sardana macros can be found here. The complete macro API can be found here.

Door

Door is an entry point to the MacroServer. Door exposes an interface to run macros and interact with the macro currently being run.

A single Macro Server can have many active Doors at the same time but a Door can only run one macro at a time. Each Door is exposed on the sardana server as a Tango device.

There exists a possibility to define macro environment applied at the door level i.e. not visible on other doors.

Macro execution model

Each Door owns a macro executor which runs macros asynchronously in a dedicated thread (internally, a thread pool of size one per Door). This design has two consequences:

  • the Door device itself stays responsive while a macro runs — clients can query its state, receive its output and logs as events, and request a stop, abort or pause at any time;

  • macros within one Door are strictly serialized, while different Doors run their macros concurrently and independently. The total number of macros that can execute concurrently in a Macro Server is limited by its MaxParallelMacros Tango device property.

Macros may call other macros (e.g. a scan macro moving motors via the mv macro). A sub-macro does not get a new thread: it runs synchronously inside its parent’s thread, and the executor maintains a macro stack recording the parent-child chain. Every macro object knows its parent macro, which is used, among other things, for cascading interruption requests, log propagation and environment lookup in the parent macro’s scope.

Hooks

Macros which inherit from the Hookable class advertise hook places — named points in their execution (e.g. pre-scan, post-acq, post-scan in the scan macros) where arbitrary user code, a Python callable or another macro, can be attached without modifying the hooked macro. This is the standard extension mechanism for customizing standard procedures: instead of copying and editing a scan macro, a user attaches behavior to the places the macro exposes. The concept and the ways of attaching hooks (programmatically, via the general hooks mechanism or in sequences) are described in Macro Hooks.

Stop, abort and pause semantics

A running macro can be interrupted at any time, with two clearly distinguished flavors. Stop is the gentle one: it is supposed to bring the system to a stable state according to a particular protocol, respecting the nominal configuration — e.g. stopping motors with their nominal deceleration. Abort is foreseen for emergency situations and brings the system to a stable state as fast as possible — e.g. stopping motors instantly, possibly losing track of position.

The interruption acts on two levels. Objects reserved by the macro (e.g. motors obtained through the macro API) are stopped or aborted immediately. The macro code itself, however, only terminates when its thread reaches the next macro API call acting as a checkpoint; the interruption is then delivered as a Python exception raised inside the macro thread. Macros can install custom cleanup behavior by overriding the on_stop / on_abort methods. The full semantics, including the interplay with exception handling in macro code, are described in Handling macro stop and abort; how to issue the interruption from Spock is described in Stopping macros.

Macros can also be paused at checkpoints and later resumed, e.g. to let a user inspect an intermediate state before continuing.

Environment

The Macro Server provides a persistent key-value store, the environment, which macros use to share state and configuration across executions (e.g. the active measurement group, scan file settings). Environment variables can be scoped globally, per door and per macro. The concept and its usage are described in Macro Environment; the design of the pluggable storage backends (shelve, Redis, Tango database) is discussed in Environment storage backends.